Skip to content
This repository was archived by the owner on May 17, 2023. It is now read-only.

Commit

Permalink
add CTR mode
Browse files Browse the repository at this point in the history
  • Loading branch information
Johannes Reuter committed Mar 31, 2018
1 parent ab95da8 commit a650203
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 9 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# aes-wasm

This library is a WASM port of the [ghostscript AES implementation](https://www.ghostscript.com/doc/base/aes.c).
Currently only the CBC mode is supported.
Currently CBC and CTR mode are supported.

You can use this library like any other JS dependency in your project by running
```sh
Expand Down Expand Up @@ -40,23 +40,25 @@ aes256().then(aesApi => {

The API object has three functions for initialization, encryption and decryption:

### init(key, iv)
### init(key, iv, mode = 'CBC')

Initializes the instance with a key and an IV. The `key` has to be a `TypedArray`, an `Array` or an array-like object
containing the byte values of the AES key (16 items for aes128, 24 items for aes192 and 32 items for aes256).

The `iv` has to be a `TypedArray`, an `Array` or an array-like object containing the byte values of the AES IV (16 items).

`mode` defines the encryption/decryption mode as `string` - supported values are `CBC` and `CTR`.

### encrypt(plainData): encryptedData

Encrypts a given set of data which has to be a `TypedArray`, an `Array` or an array-like object containing the byte values
of the data block. The size of the data block has to be divisble by 16 (padding is not included yet).
of the data block. The size of the data block has to be divisble by 16 if the `CBC` mode is used (padding is not included yet).
A `TypedArray` will yield the best performance as it doesn't have to be converted prior to use.
Returns a `TypedArray` containing the encrypted data.

### decrypt(plainData): plainData

Decrypts a given set of encrypted data which has to be a `TypedArray`, an `Array` or an array-like object containing the byte values
of the data block. The size of the data block has to be divisble by 16 (padding is not included yet).
of the data block. The size of the data block has to be divisble by 16 if the `CBC` mode is used (padding is not included yet).
A `TypedArray` will yield the best performance as it doesn't have to be converted prior to use.
Returns a `TypedArray` containing the decrypted data.
36 changes: 36 additions & 0 deletions c/aes.c
Original file line number Diff line number Diff line change
Expand Up @@ -832,4 +832,40 @@ void aes_crypt_cfb( aes_context *ctx,
}

*iv_off = n;
}

void aes_crypt_ctr(aes_context *ctx,
int length,
unsigned char iv[16],
const unsigned char *input,
unsigned char *output)
{
unsigned char temp[16];
int bi;
int i;

while( length > 0 )
{
aes_crypt_ecb( ctx, AES_ENCRYPT, iv, temp);

for( i = 0; i < 16; i++ )
output[i] = (unsigned char)( input[i] ^ temp[i] );

input += 16;
output += 16;
length -= 16;

for (bi = 15; bi >= 0; --bi)
{
/* inc will owerflow */
if (iv[bi] == 255)
{
iv[bi] = 0;
continue;
}
iv[bi] += 1;
break;
}
bi = 0;
}
}
6 changes: 6 additions & 0 deletions c/aes.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ void aes_crypt_cfb( aes_context *ctx,
const unsigned char *input,
unsigned char *output );

void aes_crypt_ctr( aes_context *ctx,
int length,
unsigned char iv[16],
const unsigned char *input,
unsigned char *output );

#ifdef __cplusplus
}
#endif
Expand Down
2 changes: 1 addition & 1 deletion emscripten.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ mkdir src/wasm
emcc ./c/aes.c \
-s WASM=1 \
-O3 \
-s "EXPORTED_FUNCTIONS=['_aes_setkey_dec', '_aes_setkey_enc', '_aes_crypt_cbc']" \
-s "EXPORTED_FUNCTIONS=['_aes_setkey_dec', '_aes_setkey_enc', '_aes_crypt_cbc', '_aes_crypt_ctr']" \
-o src/wasm/aes.js
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "aes-wasm",
"version": "1.0.4",
"version": "1.1.0",
"main": "index.js",
"license": "MIT",
"repository": {
Expand Down
18 changes: 15 additions & 3 deletions src/glue.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,38 @@ export default (wasmModule, keySize) => async () => {
// rest of the memory
const blockPointer = malloc(0);

let currentMode;

function loadData(data) {
const byteData = coerceArray(data);
byteView.set(byteData, blockPointer);
}

return {
init: (key, iv) => {
init: (key, iv, mode = 'CBC') => {
byteView.set(iv, ivPointer);
byteView.set(key, keyPointer);
instance.exports._aes_setkey_enc(encryptionContextPointer, keyPointer, keySize);
instance.exports._aes_setkey_dec(decryptionContextPointer, keyPointer, keySize);
currentMode = mode;
},
encrypt: data => {
loadData(data);
instance.exports._aes_crypt_cbc(encryptionContextPointer, 1, data.length, ivPointer, blockPointer, blockPointer);
if(currentMode === 'CBC') {
instance.exports._aes_crypt_cbc(encryptionContextPointer, 1, data.length, ivPointer, blockPointer, blockPointer);
} else {
instance.exports._aes_crypt_ctr(encryptionContextPointer, data.length, ivPointer, blockPointer, blockPointer);
}

return byteView.subarray(blockPointer, blockPointer + data.length).slice();
},
decrypt: data => {
loadData(data);
instance.exports._aes_crypt_cbc(decryptionContextPointer, 0, data.length, ivPointer, blockPointer, blockPointer);
if(currentMode === 'CBC') {
instance.exports._aes_crypt_cbc(decryptionContextPointer, 0, data.length, ivPointer, blockPointer, blockPointer);
} else {
instance.exports._aes_crypt_ctr(encryptionContextPointer, data.length, ivPointer, blockPointer, blockPointer);
}
return byteView.subarray(blockPointer, blockPointer + data.length).slice();
}
};
Expand Down
28 changes: 28 additions & 0 deletions test/aes128.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,32 @@ describe('aes128', () => {
expect(largeDecryptedBuffer).toEqual(largePlainBuffer);
});
});

describe('CTR', () => {
const plainBuffer = coerceArray([0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff,
0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e, 0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab,
0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1, 0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee]);
const encryptedBuffer = coerceArray([0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
]);
const ivBuffer = coerceArray([0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff]);
const keyBuffer = coerceArray([0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c]);

it('should encrypt CTR correctly', async () => {
const crypt = await aes128();
crypt.init(keyBuffer, ivBuffer, 'CTR');
const result = crypt.encrypt(plainBuffer);
expect(result).toEqual(encryptedBuffer);
});

it('should decrypt CTR correctly', async () => {
const crypt = await aes128();
crypt.init(keyBuffer, ivBuffer, 'CTR');
const result = crypt.decrypt(encryptedBuffer);
expect(result).toEqual(plainBuffer);
});
});
});
30 changes: 30 additions & 0 deletions test/aes192.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,34 @@ describe('aes192', () => {
expect(largeDecryptedBuffer).toEqual(largePlainBuffer);
});
});


describe('CTR', () => {
const plainBuffer = coerceArray([0x1a, 0xbc, 0x93, 0x24, 0x17, 0x52, 0x1c, 0xa2, 0x4f, 0x2b, 0x04, 0x59, 0xfe, 0x7e, 0x6e, 0x0b,
0x09, 0x03, 0x39, 0xec, 0x0a, 0xa6, 0xfa, 0xef, 0xd5, 0xcc, 0xc2, 0xc6, 0xf4, 0xce, 0x8e, 0x94,
0x1e, 0x36, 0xb2, 0x6b, 0xd1, 0xeb, 0xc6, 0x70, 0xd1, 0xbd, 0x1d, 0x66, 0x56, 0x20, 0xab, 0xf7,
0x4f, 0x78, 0xa7, 0xf6, 0xd2, 0x98, 0x09, 0x58, 0x5a, 0x97, 0xda, 0xec, 0x58, 0xc6, 0xb0, 0x50]);
const encryptedBuffer = coerceArray([0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
]);
const ivBuffer = coerceArray([0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff]);
const keyBuffer = coerceArray([0x8e, 0x73, 0xb0, 0xf7, 0xda, 0x0e, 0x64, 0x52, 0xc8, 0x10, 0xf3, 0x2b, 0x80, 0x90, 0x79, 0xe5,
0x62, 0xf8, 0xea, 0xd2, 0x52, 0x2c, 0x6b, 0x7b]);

it('should encrypt CTR correctly', async () => {
const crypt = await aes192();
crypt.init(keyBuffer, ivBuffer, 'CTR');
const result = crypt.encrypt(plainBuffer);
expect(result).toEqual(encryptedBuffer);
});

it('should decrypt CTR correctly', async () => {
const crypt = await aes192();
crypt.init(keyBuffer, ivBuffer, 'CTR');
const result = crypt.decrypt(encryptedBuffer);
expect(result).toEqual(plainBuffer);
});
});
});
29 changes: 29 additions & 0 deletions test/aes256.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,33 @@ describe('aes256', () => {
expect(largeDecryptedBuffer).toEqual(largePlainBuffer);
});
});

describe('CTR', () => {
const plainBuffer = coerceArray([0x60, 0x1e, 0xc3, 0x13, 0x77, 0x57, 0x89, 0xa5, 0xb7, 0xa7, 0xf5, 0x04, 0xbb, 0xf3, 0xd2, 0x28,
0xf4, 0x43, 0xe3, 0xca, 0x4d, 0x62, 0xb5, 0x9a, 0xca, 0x84, 0xe9, 0x90, 0xca, 0xca, 0xf5, 0xc5,
0x2b, 0x09, 0x30, 0xda, 0xa2, 0x3d, 0xe9, 0x4c, 0xe8, 0x70, 0x17, 0xba, 0x2d, 0x84, 0x98, 0x8d,
0xdf, 0xc9, 0xc5, 0x8d, 0xb6, 0x7a, 0xad, 0xa6, 0x13, 0xc2, 0xdd, 0x08, 0x45, 0x79, 0x41, 0xa6]);
const encryptedBuffer = coerceArray([0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
]);
const ivBuffer = coerceArray([0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff]);
const keyBuffer = coerceArray([0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4]);

it('should encrypt CTR correctly', async () => {
const crypt = await aes256();
crypt.init(keyBuffer, ivBuffer, 'CTR');
const result = crypt.encrypt(plainBuffer);
expect(result).toEqual(encryptedBuffer);
});

it('should decrypt CTR correctly', async () => {
const crypt = await aes256();
crypt.init(keyBuffer, ivBuffer, 'CTR');
const result = crypt.decrypt(encryptedBuffer);
expect(result).toEqual(plainBuffer);
});
});
});

0 comments on commit a650203

Please sign in to comment.