この記事の目的
C# を用いて、
- 暗号用乱数生成器によるIV (初期化ベクトル) の生成
- 暗号化・復号 (AES)
- ハッシュ化 (SHA-256)
を行い、一連の操作を行う実用的なサンプルを作成する。
平文にそのハッシュ値を連結したデータを暗号化し、復号時にハッシュ値を検証することで暗号文が有効かを確認できるようにする。
この記事では、それぞれのクラスのメソッドなどは今回のサンプルプログラムで使用したもののみを紹介している。
それぞれのクラスには、他のメソッド (同じ名前でとる引数が異なるメソッドを含む) も存在する。
暗号用乱数生成
RandomNumberGenerator クラスを用いることで、暗号用の乱数を生成できる。
まず、Create() 静的メソッドにより、このクラスのオブジェクトを生成する。
次に、生成したオブジェクトの GetBytes() メソッドを用いると、指定した byte[]
配列を乱数列で埋めることができる。
RandomNumberGenerator rng = RandomNumberGenerator.Create();
byte[] randomBytes = new byte[16];
rng.GetBytes(randomBytes); // 配列をランダムデータで埋める
暗号化・復号
Aes クラスを用いることで、バイト列の AES による暗号化や復号を行うことができる。
まず、Create() 静的メソッドにより、このクラスのオブジェクトを生成する。
デフォルトでは、CBC モードおよび PKCS7 パディングを用いる。
次に、生成したオブジェクトを用いて、暗号化時は CreateEncryptor() メソッド、復号時は CreateDecryptor() メソッドにより ICryptoTransform インターフェイスのオブジェクトを生成する。
これらのメソッドは、引数として鍵と IV (いずれも byte[]
) をとる。
この ICryptoTransform
オブジェクトの TransformFinalBlock() メソッドにデータを渡すことで、データの暗号化や復号ができる。
このとき、データが格納された byte[]
配列に加え、暗号化や復号を行う最初の要素の添字と要素数を指定する。
byte[] plainText = new byte[20]; // 平文:長さはブロックサイズの倍数じゃなくてもOK
byte[] key = new byte[16]; // 鍵:16バイト・24バイト・32バイトのいずれか
byte[] iv = new byte[16]; // IV:16バイト
Aes aes = Aes.Create();
// 暗号化を行う変換器を取得する
ICryptoTransform encryptor = aes.CreateEncryptor(key, iv);
// 配列 plainText 全体を暗号化する
byte[] cipherText = encryptor.TransformFinalBlock(plainText, 0, plainText.Length);
ハッシュ化
SHA256 クラスを用いることで、バイト列の SHA-256 ハッシュ値を求めることができる。
まず、Create() 静的メソッドにより、このクラスのオブジェクトを生成する。
次に、生成したオブジェクトの ComputeHash() メソッドを用いると、指定したバイト列 (byte[]
配列) の SHA-256 ハッシュ値を求め、byte[]
配列で得ることができる。
また、HashSize プロパティにより、生成するハッシュ値のビット数を求めることができる。
SHA256 sha = SHA256.Create();
int size = sha.HashSize / 8; // SHA-256 ハッシュ値のバイト数
byte[] data = new byte[]{0xde, 0xad, 0xbe, 0xef}; // ハッシュ化するデータ
byte[] hash = sha.ComputeHash(data); // SHA-256 ハッシュ値を求める
その他
今回のサンプルプログラムの作成にあたり、以下を参考にした。
- 配列の結合
- 配列の内容の比較 (一致判定)
サンプルプログラム
using System;
using System.Linq;
using System.Security.Cryptography;
class EncryptTest
{
private static Aes aes = Aes.Create();
private static RandomNumberGenerator rng = RandomNumberGenerator.Create();
private static SHA256 sha = SHA256.Create();
// バイト配列 a と b を結合した配列を返す
private static byte[] ConcatArray(byte[] a, byte[] b)
{
byte[] res = new byte[a.Length + b.Length];
a.CopyTo(res, 0);
b.CopyTo(res, a.Length);
return res;
}
// 平文 data を鍵 key で暗号化した結果を返す
public static byte[] Encrypt(byte[] data, byte[] key)
{
// IVを生成する
byte[] iv = new byte[16];
rng.GetBytes(iv);
// SHA-256ハッシュ値を求める
byte[] hash = sha.ComputeHash(data);
// データとハッシュ値を暗号化する
ICryptoTransform encryptor = aes.CreateEncryptor(key, iv);
byte[] plainText = ConcatArray(data, hash);
byte[] cipherText = encryptor.TransformFinalBlock(plainText, 0, plainText.Length);
// IVと暗号文を連結して返す
byte[] result = ConcatArray(iv, cipherText);
return result;
}
// 暗号文 data を鍵 key で復号した結果を返す
public static byte[] Decrypt(byte[] data, byte[] key)
{
// IVを取り出す
byte[] iv = new byte[16];
Array.Copy(data, 0, iv, 0, iv.Length);
// データとハッシュ値を復号する
ICryptoTransform decryptor = aes.CreateDecryptor(key, iv);
byte[] plainText = decryptor.TransformFinalBlock(data, iv.Length, data.Length - iv.Length);
// データとハッシュ値を分離する
byte[] decryptedData = new byte[plainText.Length - sha.HashSize / 8];
byte[] decryptedHash = new byte[plainText.Length - decryptedData.Length];
Array.Copy(plainText, 0, decryptedData, 0, decryptedData.Length);
Array.Copy(plainText, decryptedData.Length, decryptedHash, 0, decryptedHash.Length);
// データのSHA-256ハッシュ値を求める
byte[] hash = sha.ComputeHash(decryptedData);
// 求めたハッシュ値を復号したハッシュ値と比較する
if (!hash.SequenceEqual(decryptedHash))
{
throw new Exception("hash mismatch");
}
// 復号したデータを返す
return decryptedData;
}
}
using System;
using System.Text;
using System.Text.RegularExpressions;
class EncryptTestUser
{
// バイト列を、16バイトごとに改行を入れた文字列に変換する
private static string BytesToString(byte[] data)
{
string rawResult = BitConverter.ToString(data);
Regex regex = new Regex("(([0-9a-fA-F]{2}-){15}[0-9a-fA-F]{2})-");
return regex.Replace(rawResult, "$1\n");
}
public static void Main(string[] args)
{
byte[] key = new byte[] {
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
};
Console.WriteLine("key:");
Console.WriteLine(BytesToString(key));
string plainText = "hello, world";
Console.WriteLine("\nplain text:");
Console.WriteLine(plainText);
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
Console.WriteLine("\nplain text (bytes):");
Console.WriteLine(BytesToString(plainTextBytes));
byte[] cipherText = EncryptTest.Encrypt(plainTextBytes, key);
Console.WriteLine("\ncipher text (bytes):");
Console.WriteLine(BytesToString(cipherText));
byte[] decryptedTextBytes = EncryptTest.Decrypt(cipherText, key);
Console.WriteLine("\ndecrypted text (bytes):");
Console.WriteLine(BytesToString(decryptedTextBytes));
string decryptedText = Encoding.UTF8.GetString(decryptedTextBytes);
Console.WriteLine("\ndecrypted text:");
Console.WriteLine(decryptedText);
}
}
key:
01-23-45-67-89-AB-CD-EF-00-11-22-33-44-55-66-77
88-99-AA-BB-CC-DD-EE-FF-FE-DC-BA-98-76-54-32-10
plain text:
hello, world
plain text (bytes):
68-65-6C-6C-6F-2C-20-77-6F-72-6C-64
cipher text (bytes):
A1-2E-9C-D5-9D-02-BC-A5-A7-43-EC-72-86-3F-49-3C
D5-8E-04-CB-6F-8B-4E-F8-45-7E-89-AC-A0-C6-DC-9F
38-10-BF-C9-74-94-80-3D-F3-30-87-7D-65-57-30-97
04-BB-75-E5-B5-39-EB-12-AD-D9-78-44-C1-D7-DF-B8
decrypted text (bytes):
68-65-6C-6C-6F-2C-20-77-6F-72-6C-64
decrypted text:
hello, world
まとめ
C# で暗号用乱数生成・暗号化 (AES)・ハッシュ化 (SHA-256) を行う方法を確認した。
さらに、これらを用いて実際に暗号化や復号を行うサンプルを作成した。