EncodeクラスではCreateSalt(string str)メソッドでソルトを生成するとともに、初期化ベクトルを生成していました。そしてパスワードとソルトから暗号鍵を生成していました。そしてCreateSalt(string str)メソッドとCreateIV(string str)メソッドは以下のような内容になっていました。
1 2 3 4 5 6 |
// ソルトを生成する static private byte[] CreateSalt(string str) { Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes("8文字以上の文字列を指定すること", SALT_SIZE); return deriveBytes.Salt; } |
1 2 3 4 5 6 |
// 初期化ベクトルを生成する static private byte[] CreateIV(string str) { Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes("8文字以上の文字列を指定すること", 100); return deriveBytes.GetBytes(BLOCK_SIZE / 8); } |
引数が渡されていますが、使用されていません。Rfc2898DeriveBytesコンストラクタに渡されているのは”8文字以上の文字列を指定すること”という定まった文字列と生成するソルトのサイズだけです。これを利用して以下の方法で暗号鍵を生成するのはどうなのでしょうか?
1 2 3 4 5 6 7 8 9 |
//パスワードとソルトから暗号鍵を求める //(渡されるソルトは実行するたびに異なるため、生成される暗号鍵も異なるが、 // ソルト自体は同じ文字列から生成されたものである) static private byte[] GetKeyFromPassword(string password, byte[] salt) { Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(password, salt); deriveBytes.IterationCount = 1000; return deriveBytes.GetBytes(KEY_SIZE / 8); } |
たしかにソルトを生成するときは同じ文字列でも異なったものが生成されます。でもそうであってもソルト生成のために渡す文字列は不規則に変化させたほうがよいと思われます。
暗号化ソフトのなかには説明書に、
1 |
[暗号化]のボタンを押すまえにマウスを不規則に動かしてください、できるだけ長時間動かすようにしてください。 |
と書かれているものがあります。マウスを動かすクセというのは人によって違うのでコンピュータが作り出す疑似乱数よりもランダムなデータが取得できそうです。そこでこれまで作ってきた暗号化の処理を改良してみることにします。
これは
で作成したものですが、マスターパスワードを生成するダイアログを表示させているときにユーザーに不規則にマウスを動かさせるのはどうでしょうか?
で作成した文字列を暗号化するクラスを少し修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
// 文字列を暗号化するクラス public class Encode { static public string EncodeString(string srcString, string password, string randomString1, string randomString2) { // ソルトは SALT_SIZEバイト byte[] salt = CreateSalt(randomString1); // IVは BLOCK_SIZE / 8バイト byte[] iv = CreateIV(randomString2); // 暗号鍵は KEY_SIZE / 8バイト byte[] key = GetKeyFromPassword(password, salt); byte[] outBytes = null; using(AesManaged aes = new AesManaged()) { // AESインスタンスのパラメータ設定 aes.KeySize = KEY_SIZE; aes.BlockSize = BLOCK_SIZE; aes.Mode = CipherMode.CBC; aes.Key = key; aes.IV = iv; aes.Padding = PaddingMode.ISO10126; using(ICryptoTransform ct = aes.CreateEncryptor(aes.Key, aes.IV)) // 暗号化オブジェクト生成 using(var outStream = new System.IO.MemoryStream()) // 出力ストリーム { using(CryptoStream cryptoStream = new CryptoStream(outStream, ct, CryptoStreamMode.Write)) { byte[] buf = Encoding.Unicode.GetBytes(srcString); cryptoStream.Write(buf, 0, buf.Length); } outBytes = outStream.ToArray(); } } // ソルト、IV、暗号化されたデータをつなげてBase64に変換 List<byte> vs = new List<byte>(); vs.AddRange(salt); vs.AddRange(iv); vs.AddRange(outBytes); return Convert.ToBase64String(vs.ToArray()); } static private byte[] CreateSalt(string str) { Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(str, SALT_SIZE); return deriveBytes.Salt; } //初期化ベクトルを作成する static private byte[] CreateIV(string str) { Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(str, BLOCK_SIZE / 8); return deriveBytes.GetBytes(BLOCK_SIZE / 8); } // 上記以外のメソッドやフィールド変数は同じ } |
暗号化するときにランダムな文字列が必要なのでコンストラクタの引数の数が増えました。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Doc { public Doc(string masterPassword, List<Data> datas, string randomString1, string randomString2) { foreach(Data data in datas) { data.EncodeData(masterPassword, randomString1, randomString2); } EncodedDatas = datas; } // 上記以外のメソッドやフィールド変数は同じ } |
Docクラス同様、暗号化するときにランダムな文字列が必要なのでコンストラクタの引数の数が増えました。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Config { public Config(string masterPassword, string mailServer, string port, string id, string password, bool isAll, string froms, string randomString1, string randomString2) { MasterPasswordHash = GetPasswordHash(masterPassword); EncodeMailServer = Encode.EncodeString(mailServer, masterPassword, randomString1, randomString2); EncodePort = Encode.EncodeString(port, masterPassword, randomString1, randomString2); EncodeId = Encode.EncodeString(id, masterPassword, randomString1, randomString2); EncodePassword = Encode.EncodeString(password, masterPassword, randomString1, randomString2); IsAll = isAll; EncodeFroms = Encode.EncodeString(froms, masterPassword, randomString1, randomString2); } } |
Dataクラスも暗号化するときにランダムな文字列が必要なので暗号化処理をするメソッドの引数の数が増えました。
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Data { public void EncodeData(string password, string randomString1, string randomString2) { EncodedSubject = Encode.EncodeString(Subject, password, randomString1, randomString2); EncodedDateText = Encode.EncodeString(DateText, password, randomString1, randomString2); EncodedMainText = Encode.EncodeString(MainText, password, randomString1, randomString2); EncodedFrom = Encode.EncodeString(From, password, randomString1, randomString2); } // 上記以外のメソッドやフィールド変数は同じ } |
あとは設定を保存するときにランダムな文字列を作成して各コンストラクタに渡すだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
public partial class Form1 : Form { void SaveConfig() { SaveFileDialog dlg = new SaveFileDialog(); dlg.Filter = "設定ファイル(*.xml)|*.xml"; dlg.DefaultExt = "xml"; if(dlg.ShowDialog() == DialogResult.OK) { FormSavePass f = new FormSavePass(); f.ShowDialog(); if(f.Password != "") { // ランダムに生成された文字列をConfigクラスのコンストラクタに渡す string randomString1 = f.RandomString1; string randomString2 = f.RandomString2; Config config = new Config(f.Password, textBoxMailServer.Text, textBoxPort.Text, textBoxId.Text, textBoxPass.Text, checkBox1.Checked, textBoxMallAdresses.Text, randomString1, randomString2); System.Xml.Serialization.XmlSerializer xml = new System.Xml.Serialization.XmlSerializer(typeof(Config)); System.IO.StreamWriter sw = new System.IO.StreamWriter(dlg.FileName); xml.Serialize(sw, config); sw.Close(); MessageBox.Show("設定ファイル保存完了"); } f.Dispose(); } dlg.Dispose(); } void SaveMails() { SaveFileDialog dlg = new SaveFileDialog(); dlg.Filter = "メールデータ(*.xml)|*.xml"; dlg.DefaultExt = "xml"; if(dlg.ShowDialog() == DialogResult.OK) { FormSavePass f = new FormSavePass(); f.ShowDialog(); if(f.Password != "") { string randomString1 = f.RandomString1; string randomString2 = f.RandomString2; Doc doc = new Doc(f.Password, Datas, randomString1, randomString2); System.Xml.Serialization.XmlSerializer xml = new System.Xml.Serialization.XmlSerializer(typeof(Doc)); System.IO.StreamWriter sw = new System.IO.StreamWriter(dlg.FileName); xml.Serialize(sw, doc); sw.Close(); MessageBox.Show("メールを保存しました!"); } f.Dispose(); } dlg.Dispose(); } } |
以下は設定を暗号化してファイルに保存するときに表示されるフォームです。
マウスがフォームの外側にあるときの動きも捕捉したいので、マウスフックをしています。
マウスが動いたらその座標を取得します。前の座標とある程度変化していたらその座標をリストに格納します。そしてマウス座標から文字を生成します。
そのときはマウス座標に 0x4000をプラスして文字コードからランダムな文字列を作り出しています。得られる文字は以下のようなものです。
Unicode一覧 4000-4FFF – Wikipedia
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
public partial class FormSavePass : Form { public FormSavePass() { InitializeComponent(); buttonOK.Click += ButtonOK_Click; textBox1.UseSystemPasswordChar = true; textBox2.UseSystemPasswordChar = true; hook.MouseMoveEvent += Hook_MouseMoveEvent; } MouseHook hook = new MouseHook(); public string Password = ""; public string RandomString1 = ""; public string RandomString2 = ""; List<int> xPos = new List<int>(); List<int> yPos = new List<int>(); int lastXpos = 0; int lastYpos = 0; private void Hook_MouseMoveEvent(object sender, MouseEventArg e) { if(Math.Abs(lastXpos - e.Point.X) > 20 && xPos.Count < 1000) { xPos.Add(e.Point.X); lastXpos = e.Point.X; } if(Math.Abs(lastYpos - e.Point.Y) > 20 && yPos.Count < 1000) { yPos.Add(e.Point.Y); lastYpos = e.Point.Y; } } char GetCharRromInt(int i) { i += 0x4000; return (char)i; } string GetStringRromInts(List<int> vs) { List<char> chars = new List<char>(); foreach(int i in vs) { chars.Add(GetCharRromInt(i)); } string ret = new String(chars.ToArray()); if(ret.Length < 8) { ret += "8文字に足りていないときは文字列を追加する"; } return ret; } private void ButtonOK_Click(object sender, EventArgs e) { if(textBox1.Text != "" && textBox1.Text == textBox2.Text) { Password = textBox1.Text; RandomString1 = GetStringRromInts(xPos); RandomString2 = GetStringRromInts(yPos); Close(); } else { MessageBox.Show("パスワードが正しく設定できていません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } } protected override void OnLoad(EventArgs e) { hook.Hook(); } protected override void OnClosed(EventArgs e) { hook.UnHook(); base.OnClosed(e); } } |