using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
namespace TwlBackupBlock
{
///
/// AES-CMACによるMACを生成します。
/// 鍵は16バイト、24バイト、32バイトに対応しています。
///
public class AesCmac
{
private const int BLOCK_SIZE = 16;
private static readonly int[] LEGAL_KEY_SIZES = { 16, 24, 32 };
///
/// 指定された鍵を利用してデータのMACを取得します。
///
/// MACを発行したいデータ。
/// AES-CMACの鍵。
/// に対するMAC。
public static byte[] GetMac(byte[] data, byte[] key)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
if (key == null)
{
throw new ArgumentNullException("key");
}
if (!CheckKeySize(key.Length))
{
string message = string.Format("key.Length is illegal (key.Length:{0})", key.Length);
throw new ArgumentException("key", message);
}
var context = new AesCmacContext(block => Encrypt(block, key));
context.Update(data);
return context.GetMac();
}
///
/// 指定された鍵を利用して16バイトブロックをAESで暗号化します。
///
/// 暗号化するブロック。メソッド呼出し後は、暗号化されたブロック。
/// AESの鍵。
private static void Encrypt(byte[] block, byte[] key)
{
Debug.Assert(block.Length == BLOCK_SIZE);
Debug.Assert(CheckKeySize(key.Length));
using (var ms = new MemoryStream(block))
{
var aes = new RijndaelManaged();
aes.BlockSize = BLOCK_SIZE * 8;
aes.Mode = CipherMode.ECB;
aes.Key = key;
using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Read))
{
cs.Read(block, 0, block.Length);
}
}
}
///
/// 鍵のサイズが16バイト、24バイト、32バイトのいずれであるか判定します。
///
/// 鍵のバイトサイズ
///
private static bool CheckKeySize(int keySize)
{
return LEGAL_KEY_SIZES.Any(e => (e == keySize));
}
///
/// AES-CMAC生成器です。データの入力履歴に応じたMAC生成器の内部状態を保持します。
///
private class AesCmacContext
{
public delegate void Encrypt(byte[] block);
private Encrypt encryptOperation;
private byte[] block;
private int filledIndex;
///
/// 指定された暗号化処理をもとに、AesCmacContextクラスの新しいインスタンスを初期化します。
///
/// 16バイトブロックを暗号化するメソッド
public AesCmacContext(Encrypt op)
{
encryptOperation = op;
block = Enumerable.Repeat(0, BLOCK_SIZE).ToArray();
filledIndex = 0;
}
///
/// 指定された入力データによりMAC生成器を更新します。
///
/// MACを発行したいデータ
public void Update(byte[] data)
{
for (int i = 0; i < data.Length; )
{
if (filledIndex >= BLOCK_SIZE)
{
encryptOperation(block);
filledIndex = 0;
}
while (filledIndex < BLOCK_SIZE && i < data.Length)
{
block[filledIndex] ^= data[i];
++filledIndex;
++i;
}
}
}
///
/// 現在までの入力履歴からMACを生成します。
///
/// これまでの入力データに対するMAC
public byte[] GetMac()
{
byte[] padding = Enumerable.Repeat(0, BLOCK_SIZE).ToArray();
encryptOperation(padding);
Multiply128(padding);
if (filledIndex < BLOCK_SIZE)
{
block[filledIndex] ^= 0x80;
Multiply128(padding);
}
for (int i = 0; i < BLOCK_SIZE; ++i)
{
padding[i] ^= block[i];
}
encryptOperation(padding);
return padding;
}
///
/// 16バイトの整数に128を乗じます。オーバーフローは無視します。
///
/// 16バイト整数
private void Multiply128(byte[] block)
{
bool isCarried = ((block[0] & 0x80) != 0);
for (int i = 0; i < block.Length - 1; i++)
{
block[i] = (byte)(((uint)block[i] << 1) | ((uint)block[i + 1] >> 7));
}
block[block.Length - 1] <<= 1;
if (isCarried)
{
block[block.Length - 1] = (byte)(block[block.Length - 1] ^ 0x87);
}
}
}
}
}