840 lines
22 KiB
D
840 lines
22 KiB
D
module angel.utils.cryptography.gcm.gcm;
|
|
|
|
public import angel.utils.cryptography.aead;
|
|
import angel.utils.cryptography.gcm.ghash;
|
|
import angel.utils.cryptography.gcm.multiplier;
|
|
|
|
public import angel.utils.cryptography.exceptions: InvalidCipherTextException, IllegalArgumentException;
|
|
|
|
|
|
/// Implementation of the Galois/Counter mode (GCM)
|
|
/// as described in NIST Special Publication 800-38D
|
|
///
|
|
/// Standards: NIST Special Publication 800-38D
|
|
|
|
|
|
// TODO Shoup tables
|
|
// TODO support for uneven macSize
|
|
|
|
//alias GCMEngine(T) = AEADCipherWrapper!(GCM!T); // would be nice but does not yet work
|
|
|
|
import angel.utils.cryptography.aes;
|
|
//static assert(isAEADCipher!(GCM!AES), "GCM ist not a AEADCipher.");
|
|
|
|
///
|
|
bool state; /// 1 encrypt, 0 decrypt
|
|
/// usage of OOP API:
|
|
/// auto aes_gcm = new AEADCipherWrapper!(GCM!AES)();
|
|
///
|
|
@safe
|
|
public struct GCM(T) if(is(T == void) || (isBlockCipher!T && T.blockSize == 16))
|
|
{
|
|
|
|
private enum OOP = is(T == void); // use OOP API
|
|
|
|
public enum blockSize = 16;
|
|
public enum macSize = 16;
|
|
|
|
// if T == void: use OOP API for underlying block cipher
|
|
static if(OOP) {
|
|
/**
|
|
* Params:
|
|
* c = underlying BlockCipher
|
|
*/
|
|
public this(IBlockCipher c)
|
|
in {
|
|
assert(c.blockSize() == blockSize, "GCM: block size of underlying cipher must be 128 bits!");
|
|
}
|
|
body {
|
|
blockCipher = c;
|
|
}
|
|
} else {
|
|
static assert(T.blockSize == blockSize, "GCM: block size of underlying cipher must be 128 bits!");
|
|
}
|
|
|
|
private {
|
|
|
|
static if(OOP) {
|
|
IBlockCipher blockCipher;
|
|
} else {
|
|
T blockCipher; /// underlying BlockCipher
|
|
}
|
|
|
|
GHash gHash; /// provides the multiplication in GF(2^128) by H
|
|
CircularBlockBuffer!blockSize buf; /// stores input data before processing
|
|
|
|
ubyte[blockSize] Y; /// counter
|
|
ubyte[blockSize] E0; /// E(key, Y0), needed to derive AuthTag from GHASH
|
|
ubyte[blockSize] mac; /// used to store the encrypted ghash TODO: use other buffer, e.g. E0 itself
|
|
|
|
ubyte[blockSize] initialY; /// used to reset Y
|
|
|
|
ubyte[] userKey;
|
|
ubyte[] iv;
|
|
bool initialized = false; /// True if and only if GCM has been initialized
|
|
}
|
|
|
|
public {
|
|
|
|
/// Initialize the underlying cipher.
|
|
/// Params:
|
|
/// forEncryption = true if we are setting up for encryption, false otherwise.
|
|
/// key = Secret key.
|
|
/// nonce = Number used only once.
|
|
void start(in ubyte[] key, in ubyte[] iv) nothrow @nogc
|
|
in {
|
|
assert(iv !is null, "Must provide an IV.");
|
|
}
|
|
body {
|
|
|
|
//this.forEncryption = forEncryption;
|
|
|
|
// init underyling cipher
|
|
blockCipher.start(key);
|
|
|
|
// init gHash
|
|
ubyte[blockSize] H;
|
|
H[] = 0;
|
|
blockCipher.decrypt(H,H); // calculate H=E(K,0^128);
|
|
|
|
gHash.init(H);
|
|
|
|
// init IV
|
|
if(iv.length == 12) { // 96 bit IV is optimal
|
|
Y[0..iv.length] = iv[];
|
|
Y[$-1] = 1;
|
|
}else {
|
|
gHash.updateCipherData(iv);
|
|
gHash.doFinal(Y);
|
|
}
|
|
|
|
// generate key stream used later to encrypt ghash
|
|
genNextKeyStreamBlock(E0);
|
|
|
|
initialY = Y; // remember this to reset the cipher
|
|
|
|
initialized = true;
|
|
}
|
|
|
|
|
|
|
|
static if(OOP) {
|
|
/**
|
|
* Returns: the algorithm name.
|
|
*/
|
|
string name() pure nothrow {
|
|
return blockCipher.name ~ "/GCM";
|
|
}
|
|
} else {
|
|
public enum name = T.name~"/GCM";
|
|
}
|
|
|
|
static if(OOP) {
|
|
/**
|
|
* Returns: the cipher this object wraps.
|
|
*/
|
|
IBlockCipher getUnderlyingCipher() pure nothrow @nogc {
|
|
return blockCipher;
|
|
}
|
|
} else {
|
|
/**
|
|
* Returns: the cipher this object wraps.
|
|
*/
|
|
ref T getUnderlyingCipher() pure nothrow @nogc {
|
|
return blockCipher;
|
|
}
|
|
}
|
|
|
|
/// Process additional authenticated data.
|
|
void processAADBytes(in ubyte[] aad...) nothrow @nogc
|
|
in {
|
|
assert(initialized, "not initialized");
|
|
}
|
|
body {
|
|
gHash.updateAAD(aad);
|
|
}
|
|
|
|
/// Process a block of bytes from in putting the result into out.
|
|
///
|
|
/// Params:
|
|
/// input = The input byte array.
|
|
/// output = The output buffer the processed bytes go into.
|
|
///
|
|
/// Returns:
|
|
/// Returns a slice pointing to the output data.
|
|
ubyte[] encrypt(in ubyte[] input, ubyte[] output) nothrow {
|
|
state = 1;
|
|
return processBytes(input, output);
|
|
}
|
|
ubyte[] decrypt(in ubyte[] input, ubyte[] output) nothrow {
|
|
state = 0;
|
|
return processBytes(input, output);
|
|
}
|
|
|
|
ubyte[] processBytes(in ubyte[] input, ubyte[] output) nothrow
|
|
in {
|
|
assert(initialized, "not initialized");
|
|
assert(output.length >= getUpdateOutputSize(input.length), "output buffer too short");
|
|
}
|
|
body {
|
|
|
|
import std.algorithm: min;
|
|
|
|
size_t outputBytes = 0;
|
|
|
|
const(ubyte)[] iBuf = input;
|
|
ubyte[] outPtr = output;
|
|
|
|
while(iBuf.length > 0) {
|
|
if(buf.isFull()) {
|
|
// encrypt one block
|
|
outputBlock(outPtr);
|
|
outPtr = outPtr[blockSize..$];
|
|
outputBytes += blockSize;
|
|
}
|
|
|
|
// copy max one block to the buffer
|
|
size_t procLen = buf.put(iBuf);
|
|
iBuf = iBuf[procLen..$];
|
|
}
|
|
|
|
return output[0..outputBytes];
|
|
}
|
|
|
|
|
|
/// Finish the operation. Does not append mac tag to the cipher text.
|
|
/// Mac tag does NOT get verified in decryption mode.
|
|
///
|
|
/// Params: out = space for any resulting output data.
|
|
/// Returns: number of bytes written into out.
|
|
size_t finish(ubyte[] macBuf, ubyte[] output) nothrow
|
|
in {
|
|
assert(initialized, "not initialized");
|
|
|
|
assert(output.length >= buf.length, "output buffer too small");
|
|
assert(macBuf.length == 16, "MAC buffer must be 16 bytes.");
|
|
}
|
|
body{
|
|
|
|
size_t outputBytes = 0;
|
|
|
|
// if(!forEncryption) {
|
|
// if(buf.length < macLen) {
|
|
// throw new InvalidCipherTextException("ciphertext so short that it can't even contain the MAC");
|
|
// }
|
|
// }
|
|
|
|
size_t partialBlockLen = buf.length;
|
|
|
|
ubyte[2*blockSize] lastBlocks; // last two blocks. probably not full. last few bytes are the token.
|
|
|
|
|
|
// copy partial cipher data block
|
|
buf.drainAll(lastBlocks);
|
|
|
|
assert(output.length >= partialBlockLen, "output buffer too short");
|
|
// encrypt last partial block
|
|
ubyte[2*blockSize] keyStream;
|
|
|
|
// generate two blocks of key stream
|
|
genNextKeyStreamBlock(keyStream[0..blockSize]);
|
|
genNextKeyStreamBlock(keyStream[blockSize..2*blockSize]);
|
|
|
|
output[0..partialBlockLen] = lastBlocks[0..partialBlockLen] ^ keyStream[0..partialBlockLen];
|
|
|
|
gHash.updateCipherData(state ? output[0..partialBlockLen] : lastBlocks[0..partialBlockLen]);
|
|
|
|
output = output[partialBlockLen..$];
|
|
outputBytes += partialBlockLen;
|
|
|
|
// calculate the hash
|
|
ubyte[16] mac;
|
|
gHash.doFinal(mac);
|
|
|
|
mac[] ^= E0[]; // calculate the token
|
|
|
|
macBuf[0..16] = mac[];
|
|
|
|
return outputBytes;
|
|
}
|
|
|
|
/// Returns: Return the size of the output buffer required for a processBytes an input of len bytes.
|
|
size_t getUpdateOutputSize(size_t len) nothrow @nogc pure const {
|
|
size_t total = len + buf.length;
|
|
//return (total + blockSize - 1) && (~blockSize+1);
|
|
return total - (total % blockSize);
|
|
}
|
|
|
|
|
|
/// Returns: Return the size of the output buffer required for a processBytes plus a finish with an input of len bytes.
|
|
size_t getOutputSize(size_t len) nothrow @nogc pure const {
|
|
return len;
|
|
}
|
|
|
|
/// Reset the cipher. After resetting the cipher is in the same state
|
|
/// as it was after the last init (if there was one).
|
|
void reset() nothrow
|
|
{
|
|
gHash.reset();
|
|
buf.reset();
|
|
|
|
Y = initialY;
|
|
blockCipher.reset();
|
|
}
|
|
}
|
|
|
|
|
|
private nothrow @safe @nogc {
|
|
|
|
/**
|
|
* generates the next key stream block by incrementing the counter
|
|
* and encrypting it.
|
|
*
|
|
* bufOff is set to 0
|
|
*/
|
|
void genNextKeyStreamBlock(ubyte[] buf)
|
|
in {
|
|
assert(buf.length == blockSize);
|
|
//assert(keyStreamBufOff == BLOCKSIZE, "not yet ready to generate next block");
|
|
}
|
|
body {
|
|
blockCipher.encrypt(Y,buf);
|
|
incrCounter();
|
|
}
|
|
|
|
/**
|
|
* encrypt or decrypt a block and write it to output
|
|
* update GHash
|
|
*/
|
|
void outputBlock(ubyte[] output)
|
|
in {
|
|
assert(output.length >= blockSize, "output buffer too short");
|
|
assert(buf.length >= blockSize, "not enough data in buffer");
|
|
}
|
|
body {
|
|
ubyte[blockSize] keyStream;
|
|
ubyte[blockSize] inputBuf;
|
|
genNextKeyStreamBlock(keyStream);
|
|
|
|
buf.drainBlock(inputBuf);
|
|
|
|
// encrypt the buffer
|
|
output[0..blockSize] = keyStream[0..blockSize] ^ inputBuf[0..blockSize];
|
|
|
|
// update gHash
|
|
gHash.updateCipherData(state ? output[0..blockSize] : inputBuf[0..blockSize]);
|
|
}
|
|
|
|
/**
|
|
* increment Y by 1
|
|
* treats rightmost 32 bits as uint, lsb on the right
|
|
*/
|
|
void incrCounter() {
|
|
for(uint i = blockSize -1; i >= blockSize-4; --i) {
|
|
if(++Y[i] != 0) {
|
|
break;
|
|
}
|
|
// increment next element on overflow of the previous
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/// Test with test vectors from
|
|
/// http://www.ieee802.org/1/files/public/docs2011/bn-randall-test-vectors-0511-v1.pdf
|
|
/// section 2.2.1
|
|
unittest {
|
|
import dcrypt.blockcipher.aes;
|
|
|
|
alias const(ubyte)[] octets;
|
|
|
|
octets key = cast(octets)x"AD7A2BD03EAC835A6F620FDCB506B345";
|
|
octets iv = cast(octets)x"12153524C0895E81B2C28465"; // 96 bits
|
|
|
|
GCM!AES gcm;
|
|
gcm.start(key, iv);
|
|
|
|
ubyte[48] output;
|
|
ubyte[] oBuf = output;
|
|
size_t outLen;
|
|
|
|
gcm.processAADBytes(cast(octets)x"D609B1F056637A0D46DF998D88E52E00");
|
|
|
|
outLen = gcm.processBytes(cast(octets)x"08000F101112131415161718191A1B1C", oBuf).length;
|
|
oBuf = oBuf[outLen..$];
|
|
outLen = gcm.processBytes(cast(octets)x"1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A", oBuf).length;
|
|
oBuf = oBuf[outLen..$];
|
|
|
|
outLen = gcm.processBytes(cast(octets)x"0002", oBuf).length;
|
|
oBuf = oBuf[outLen..$];
|
|
|
|
gcm.processAADBytes(cast(octets)x"B2C2846512153524C0895E81");
|
|
ubyte[16] mac;
|
|
outLen = gcm.finish(mac, oBuf);
|
|
// import std.stdio;
|
|
// writefln("%(%x%)", output);
|
|
assert(output == cast(octets)x"701AFA1CC039C0D765128A665DAB69243899BF7318CCDC81C9931DA17FBE8EDD7D17CB8B4C26FC81E3284F2B7FBA713D");
|
|
assert(mac == cast(octets)x"4F8D55E7D3F06FD5A13C0C29B9D5B880");
|
|
}
|
|
|
|
/// test decryption
|
|
/// test vectors from
|
|
/// http://www.ieee802.org/1/files/public/docs2011/bn-randall-test-vectors-0511-v1.pdf
|
|
/// section 2.2.1
|
|
unittest {
|
|
import dcrypt.blockcipher.aes;
|
|
|
|
alias const(ubyte)[] octets;
|
|
|
|
octets key = cast(octets)x"AD7A2BD03EAC835A6F620FDCB506B345";
|
|
octets iv = cast(octets)x"12153524C0895E81B2C28465"; // 96 bits
|
|
|
|
GCM!AES gcm;
|
|
gcm.start(key, iv);
|
|
|
|
ubyte[48] output;
|
|
ubyte[] oBuf = output;
|
|
size_t outLen;
|
|
|
|
gcm.processAADBytes(cast(octets)x"D609B1F056637A0D46DF998D88E52E00");
|
|
|
|
// add ciphertext
|
|
outLen = gcm.processBytes(cast(octets)
|
|
x"701AFA1CC039C0D765128A665DAB6924
|
|
3899BF7318CCDC81C9931DA17FBE8EDD
|
|
7D17CB8B4C26FC81E3284F2B7FBA713D", oBuf).length;
|
|
oBuf = oBuf[outLen..$];
|
|
|
|
gcm.processAADBytes(cast(octets)x"B2C2846512153524C0895E81");
|
|
ubyte[16] mac;
|
|
outLen = gcm.finish(mac, oBuf);
|
|
// import std.stdio;
|
|
// writefln("%(%.2x%)", output);
|
|
|
|
assert(output ==
|
|
x"08000F101112131415161718191A1B1
|
|
C1D1E1F202122232425262728292A2B
|
|
2C2D2E2F303132333435363738393A0002");
|
|
|
|
assert(mac == x"4F8D55E7D3F06FD5A13C0C29B9D5B880");
|
|
}
|
|
|
|
/// Test decryption with modified cipher data. An exception should be thrown beacause of wrong token.
|
|
///
|
|
/// test vectors from
|
|
/// http://www.ieee802.org/1/files/public/docs2011/bn-randall-test-vectors-0511-v1.pdf
|
|
/// section 2.2.1
|
|
unittest {
|
|
import dcrypt.blockcipher.aes;
|
|
|
|
alias const(ubyte)[] octets;
|
|
|
|
octets key = cast(octets)x"AD7A2BD03EAC835A6F620FDCB506B345";
|
|
octets iv = cast(octets)x"12153524C0895E81B2C28465"; // 96 bits
|
|
|
|
GCM!AES gcm;
|
|
gcm.start(key, iv);
|
|
|
|
ubyte[48] output;
|
|
ubyte[] oBuf = output[];
|
|
size_t outLen;
|
|
|
|
gcm.processAADBytes(cast(octets)x"D609B1F056637A0D46DF998D88E52E00");
|
|
|
|
// add ciphertext
|
|
outLen = gcm.processBytes(cast(octets)
|
|
x"701AFA1CC039C0D765128A665DAB6924
|
|
3899BF7318CCDC81C9931DA17FBE8EDD
|
|
7D17CB8B4C26FC81E3284F2B7FBA713D", oBuf).length; // 880 has been changed do EEF
|
|
oBuf = oBuf[outLen..$];
|
|
|
|
gcm.processAADBytes(cast(octets)x"B2C2846512153524C0895E81");
|
|
ubyte[16] mac;
|
|
outLen = gcm.finish(mac, oBuf);
|
|
assert(mac != x"4F8D55E7D3F06FD5A13C0C29B9D5BEEF");
|
|
}
|
|
|
|
/// Test decryption with altered AAD. An exception should be thrown beacause of wrong token.
|
|
///
|
|
/// test vectors from
|
|
/// http://www.ieee802.org/1/files/public/docs2011/bn-randall-test-vectors-0511-v1.pdf
|
|
/// section 2.2.1
|
|
unittest {
|
|
import dcrypt.blockcipher.aes;
|
|
|
|
alias const(ubyte)[] octets;
|
|
|
|
octets key = cast(octets)x"AD7A2BD03EAC835A6F620FDCB506B345";
|
|
octets iv = cast(octets)x"12153524C0895E81B2C28465"; // 96 bits
|
|
|
|
GCM!AES gcm;
|
|
gcm.start(key, iv);
|
|
|
|
ubyte[48] output;
|
|
ubyte[] oBuf = output;
|
|
size_t outLen;
|
|
|
|
gcm.processAADBytes(cast(octets)x"D609B1F056637A0D46DF998D88E52E00");
|
|
|
|
// add ciphertext
|
|
outLen = gcm.processBytes(cast(octets)
|
|
x"701AFA1CC039C0D765128A665DAB6924
|
|
3899BF7318CCDC81C9931DA17FBE8EDD
|
|
7D17CB8B4C26FC81E3284F2B7FBA713D", oBuf).length;
|
|
oBuf = oBuf[outLen..$];
|
|
|
|
gcm.processAADBytes(cast(octets)x"B2C2846512153524C089beef"); // changed 5E81 to beef
|
|
ubyte[16] mac;
|
|
gcm.finish(mac, oBuf);
|
|
assert(mac != x"4F8D55E7D3F06FD5A13C0C29B9D5B880");
|
|
// verify that an InvalidCipherTextException is thrown
|
|
// bool exception = false;
|
|
// try {
|
|
// outLen = gcm.finish(oBuf);
|
|
// } catch (InvalidCipherTextException e) {
|
|
// exception = true;
|
|
// }
|
|
// assert(exception, "AAD has been altered but no exception has been thrown!");
|
|
}
|
|
|
|
// test vectors from
|
|
// gcm-spec: Test Case 6
|
|
unittest {
|
|
|
|
import utils.cryptography.aes;
|
|
|
|
alias const(ubyte)[] octets;
|
|
|
|
octets key = cast(octets)x"feffe9928665731c6d6a8f9467308308";
|
|
octets iv = cast(octets)
|
|
x"9313225df88406e555909c5aff5269aa
|
|
6a7a9538534f7da1e4c303d2a318a728
|
|
c3c0c95156809539fcf0e2429a6b5254
|
|
16aedbf5a0de6a57a637b39b"; // more than 96 bits
|
|
|
|
GCM!AES gcm;
|
|
gcm.start(key, iv);
|
|
|
|
octets aad = cast(octets)(
|
|
x"feedfacedeadbeeffeedfacedeadbeef
|
|
abaddad2"
|
|
);
|
|
|
|
octets plaintext = cast(octets)(
|
|
x"d9313225f88406e5a55909c5aff5269a
|
|
86a7a9531534f7da2e4c303d8a318a72
|
|
1c3c0c95956809532fcf0e2449a6b525
|
|
b16aedf5aa0de657ba637b39"
|
|
);
|
|
|
|
ubyte[] output = new ubyte[gcm.getOutputSize(plaintext.length)];
|
|
ubyte[] oBuf = output;
|
|
size_t outLen;
|
|
|
|
outLen = gcm.processBytes(plaintext, oBuf).length;
|
|
oBuf = oBuf[outLen..$];
|
|
|
|
gcm.processAADBytes(aad);
|
|
ubyte[16] mac;
|
|
outLen = gcm.finish(mac, oBuf);
|
|
oBuf = oBuf[outLen..$];
|
|
|
|
octets expectedCiphertext = cast(octets) (
|
|
x"8ce24998625615b603a033aca13fb894
|
|
be9112a5c3a211a8ba262a3cca7e2ca7
|
|
01e4a9a4fba43c90ccdcb281d48c7c6f
|
|
d62875d2aca417034c34aee5"
|
|
);
|
|
|
|
octets expectedMac = cast(octets) x"619cc5aefffe0bfa462af43c1699d050";
|
|
|
|
assert(output == expectedCiphertext);
|
|
assert(mac == expectedMac);
|
|
}
|
|
|
|
/// test GCM with different MAC sizes
|
|
unittest {
|
|
|
|
import dcrypt.blockcipher.aes;
|
|
|
|
string[] keys = [
|
|
x"00000000000000000000000000000000",
|
|
x"00000000000000000000000000000000",
|
|
x"00000000000000000000000000000000",
|
|
x"00000000000000000000000000000000",
|
|
x"00000000000000000000000000000000",
|
|
x"00000000000000000000000000000000",
|
|
x"00000000000000000000000000000000",
|
|
x"00000000000000000000000000000000",
|
|
x"00000000000000000000000000000000",
|
|
x"00000000000000000000000000000000",
|
|
x"00000000000000000000000000000000",
|
|
x"00000000000000000000000000000000",
|
|
x"00000000000000000000000000000000",
|
|
];
|
|
string[] ivs = [
|
|
x"00",
|
|
x"00000000",
|
|
x"00000000000000",
|
|
x"00000000000000000000",
|
|
x"00000000000000000000000000",
|
|
x"00000000000000000000000000000000",
|
|
x"00000000000000000000000000000000000000",
|
|
x"00000000000000000000000000000000000000000000",
|
|
x"00000000000000000000000000000000000000000000000000",
|
|
x"00000000000000000000000000000000000000000000000000000000",
|
|
x"00000000000000000000000000000000000000000000000000000000000000",
|
|
x"00000000000000000000000000000000000000000000000000000000000000000000",
|
|
x"00000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
];
|
|
string[] aads = [
|
|
x"",
|
|
x"00000000000000",
|
|
x"0000000000000000000000000000",
|
|
x"000000000000000000000000000000000000000000",
|
|
x"00000000000000000000000000000000000000000000000000000000",
|
|
x"0000000000000000000000000000000000000000000000000000000000000000000000",
|
|
x"000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
x"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
x"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
x"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
x"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
x"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
x"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
];
|
|
string[] plains = [
|
|
x"",
|
|
x"0000000000",
|
|
x"00000000000000000000",
|
|
x"000000000000000000000000000000",
|
|
x"0000000000000000000000000000000000000000",
|
|
x"00000000000000000000000000000000000000000000000000",
|
|
x"000000000000000000000000000000000000000000000000000000000000",
|
|
x"0000000000000000000000000000000000000000000000000000000000000000000000",
|
|
x"00000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
x"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
x"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
x"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
x"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
];
|
|
string[] ciphers = [
|
|
x"3c2fa7a9",
|
|
x"078bb038e6b2353f0e05",
|
|
x"d6a480d4dec719bd36a60efde3aaf1f8",
|
|
x"e37dd3785cc7017f206df18d831e37cfe63f9e057a23",
|
|
x"3fe95bef64662ddcf19a96cc584d2146499320eef8d518bb5e7e49a7",
|
|
x"a3b22b8449afafbcd6c09f2cfa9de2be938f8bbf235863d0cefb4075046c9a4d351e",
|
|
x"a0912f3bde077afa3f21725fbcae1c9c2e00b28b6eb462745e9b65a026cc4ba84d13b408b7061fe1",
|
|
x"535b0d13cbb1012df5402f748cea5304d52db1e4b997317a54c2296b95e0300c6692f911625bfe617d16b63a237b",
|
|
x"547096f9d7a83ba8d128467baac4a9d861ebd51cc2dfff111915cd0b4260b7dc49c8d8723eb15429024ac21eed99ca1338844092",
|
|
x"95e67a9eade034290efa90e33f51710f02f3aba4c32873545891924aa52dcc092695e983b529b60e7b13aee5f7d6de278c77410e216d0fdbd7e1",
|
|
x"0957e69831df479e8cf7b214e1cef4d3e7a2716e8179deaf8061383f35eeabd017080c3d7972b98009a38b5842a2a08a9123412338e16de05a72b76849629b48",
|
|
x"07052b0f8b95c9491ae43bac6693802384688e9dd19d9ce295b4ab550163a2bb4b0dd905012a56094e895ea7a5857f8100af40b4adb6452d0b8e78e709c5c9f1d432b5f59317",
|
|
x"e0902e27a95867acaa788920ac71b2f2a61863bdc40ee869bea53470edf02fc71800465c550a58ba69220c67243899d756cf0a5ac4fda582fc6e9d2f8498a0e73e0e809bfb8d86ab5fdf066c",
|
|
];
|
|
uint[] macSizes = [
|
|
32,
|
|
40,
|
|
48,
|
|
56,
|
|
64,
|
|
72,
|
|
80,
|
|
88,
|
|
96,
|
|
104,
|
|
112,
|
|
120,
|
|
128,
|
|
];
|
|
|
|
AEADCipherTest(
|
|
new GCMEngine(new AESEngine),
|
|
keys,
|
|
ivs,
|
|
plains,
|
|
aads,
|
|
ciphers,
|
|
macSizes);
|
|
|
|
}
|
|
|
|
/// OOP Wrapper for GCM
|
|
@safe
|
|
public class GCMEngine: IAEADEngine {
|
|
|
|
private GCM!void cipher = void;
|
|
|
|
public {
|
|
|
|
/// Params: c = underlying block cipher
|
|
this(IBlockCipher c) {
|
|
cipher = GCM!void(c);
|
|
}
|
|
|
|
void start(in ubyte[] key, in ubyte[] iv) nothrow @nogc {
|
|
cipher.start(key, iv);
|
|
}
|
|
|
|
@property
|
|
string name() pure nothrow {
|
|
return cipher.name;
|
|
}
|
|
|
|
IBlockCipher getUnderlyingCipher() pure nothrow {
|
|
return cipher.getUnderlyingCipher();
|
|
}
|
|
|
|
void processAADBytes(in ubyte[] aad) nothrow {
|
|
cipher.processAADBytes(aad);
|
|
}
|
|
|
|
ubyte[] processBytes(in ubyte[] input, ubyte[] output) nothrow {
|
|
return cipher.processBytes(input, output);
|
|
}
|
|
ubyte[] encrypt(in ubyte[] input, ubyte[] output) nothrow {
|
|
state = 1;
|
|
return cipher.processBytes(input, output);
|
|
}
|
|
ubyte[] decrypt(in ubyte[] input, ubyte[] output) nothrow {
|
|
state = 0;
|
|
return cipher.processBytes(input, output);
|
|
}
|
|
|
|
size_t finish(ubyte[] macBuf, ubyte[] output) {
|
|
return cipher.finish(macBuf, output);
|
|
}
|
|
|
|
size_t getUpdateOutputSize(size_t len) nothrow const {
|
|
return cipher.getUpdateOutputSize(len);
|
|
}
|
|
|
|
size_t getOutputSize(size_t len) nothrow const {
|
|
return cipher.getOutputSize(len);
|
|
}
|
|
|
|
void reset() nothrow {
|
|
cipher.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Circular buffer holding 2*BLOCKSIZE bytes of data.
|
|
@safe
|
|
private struct CircularBlockBuffer(size_t BLOCKSIZE) {
|
|
|
|
import std.algorithm: min;
|
|
|
|
private {
|
|
ubyte[2*BLOCKSIZE] buf;
|
|
size_t offset = 0;
|
|
size_t contentLen = 0;
|
|
ubyte nextOutputBlock = 0;
|
|
}
|
|
|
|
invariant {
|
|
assert(offset <= 2*BLOCKSIZE, "offset out of bounds");
|
|
assert(contentLen <= 2*BLOCKSIZE, "contentLen out of bounds");
|
|
assert(nextOutputBlock <= 2, "nextOutputBlock out of bounds");
|
|
}
|
|
|
|
|
|
public nothrow @nogc {
|
|
|
|
/**
|
|
* try to fill the buffer
|
|
*
|
|
* Returns: number of bytes written to buffer
|
|
*/
|
|
size_t put(in ubyte[] input)
|
|
out (result){
|
|
assert(result <= input.length);
|
|
}
|
|
body {
|
|
|
|
size_t procLen = min(input.length, 2*BLOCKSIZE - contentLen);
|
|
|
|
const(ubyte)[] iBuf = input;
|
|
|
|
// copy input into buffer
|
|
foreach(i;0..procLen) {
|
|
buf[offset] = input[i];
|
|
offset = (offset + 1) % (2*BLOCKSIZE);
|
|
}
|
|
|
|
contentLen += procLen;
|
|
|
|
return procLen;
|
|
}
|
|
|
|
bool isFull() {
|
|
return contentLen == buf.length;
|
|
}
|
|
|
|
/**
|
|
* write max one block to output if buffer is full
|
|
*
|
|
* Returns: number of bytes written to output
|
|
*/
|
|
size_t drainBlock(ubyte[] output)
|
|
in {
|
|
assert(output.length >= BLOCKSIZE, "output buffer too short");
|
|
}
|
|
body {
|
|
if(isFull()) {
|
|
|
|
size_t blockOff = nextOutputBlock * BLOCKSIZE;
|
|
|
|
// copy one block to output
|
|
output[0..BLOCKSIZE] = buf[blockOff..blockOff+BLOCKSIZE];
|
|
|
|
nextOutputBlock ^= 0x01; // 0,1,0,1,...
|
|
contentLen -= BLOCKSIZE;
|
|
return BLOCKSIZE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* write whole buffer content to output
|
|
*
|
|
* Returns: number of bytes written to output
|
|
*/
|
|
size_t drainAll(ubyte[] output)
|
|
in {
|
|
assert(output.length >= contentLen, "output buffer too short");
|
|
}
|
|
body {
|
|
|
|
size_t startOff = nextOutputBlock * BLOCKSIZE;
|
|
|
|
// copy data to output
|
|
foreach(i;0..contentLen) {
|
|
output[i] = buf[(startOff + i) % (2*BLOCKSIZE)];
|
|
}
|
|
|
|
size_t outLen = contentLen;
|
|
contentLen = 0;
|
|
nextOutputBlock = 0;
|
|
offset = 0;
|
|
return outLen;
|
|
}
|
|
|
|
@property
|
|
size_t length() const {
|
|
return contentLen;
|
|
}
|
|
|
|
void reset() {
|
|
buf[] = 0;
|
|
offset = 0;
|
|
contentLen = 0;
|
|
nextOutputBlock = 0;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
} |