From 030a85199f94073376c3a4f55f06db50fbcb889d Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Thu, 13 Feb 2020 13:11:34 -0500 Subject: [PATCH] Add support for AES encrypt/decrypting sensitive values (closes #7355) --- modules/util/aes.js | 28 ++++++++++++++++++++++++ modules/util/index.js | 3 +++ package.json | 1 + test/index.html | 1 + test/spec/util/aes.js | 50 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+) create mode 100644 modules/util/aes.js create mode 100644 test/spec/util/aes.js diff --git a/modules/util/aes.js b/modules/util/aes.js new file mode 100644 index 000000000..ce40a73ed --- /dev/null +++ b/modules/util/aes.js @@ -0,0 +1,28 @@ +import aesjs from 'aes-js'; + +// See https://github.com/ricmoo/aes-js +// We can use keys that are 128 bits (16 bytes), 192 bits (24 bytes) or 256 bits (32 bytes). +// To generate a random key: window.crypto.getRandomValues(new Uint8Array(16)); + +// This default signing key is built into iD and can be used to mask/unmask sensitive values. +const DEFAULT_128 = [250, 157, 60, 79, 142, 134, 229, 129, 138, 126, 210, 129, 29, 71, 160, 208]; + + +export function utilAesEncrypt(text, key) { + key = key || DEFAULT_128; + const textBytes = aesjs.utils.utf8.toBytes(text); + const aesCtr = new aesjs.ModeOfOperation.ctr(key); + const encryptedBytes = aesCtr.encrypt(textBytes); + const encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes); + return encryptedHex; +} + + +export function utilAesDecrypt(encryptedHex, key) { + key = key || DEFAULT_128; + const encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex); + const aesCtr = new aesjs.ModeOfOperation.ctr(key); + const decryptedBytes = aesCtr.decrypt(encryptedBytes); + const text = aesjs.utils.utf8.fromBytes(decryptedBytes); + return text; +} diff --git a/modules/util/index.js b/modules/util/index.js index ce1e2ca16..b6a5f8507 100644 --- a/modules/util/index.js +++ b/modules/util/index.js @@ -1,3 +1,6 @@ +export { utilAesEncrypt } from './aes'; +export { utilAesDecrypt } from './aes'; + export { utilArrayChunk } from './array'; export { utilArrayDifference } from './array'; export { utilArrayFlatten } from './array'; diff --git a/package.json b/package.json index 222553258..948f24197 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@mapbox/vector-tile": "^1.3.1", "@turf/bbox-clip": "^6.0.0", "abortcontroller-polyfill": "^1.4.0", + "aes-js": "^3.1.2", "alif-toolkit": "^1.2.6", "browser-polyfills": "~1.5.0", "diacritics": "1.3.0", diff --git a/test/index.html b/test/index.html index 8ef4f63b8..35498a23e 100644 --- a/test/index.html +++ b/test/index.html @@ -141,6 +141,7 @@ 'spec/ui/fields/localized.js', 'spec/ui/fields/wikipedia.js', + 'spec/util/aes.js', 'spec/util/array.js', 'spec/util/clean_tags.js', 'spec/util/keybinding.js', diff --git a/test/spec/util/aes.js b/test/spec/util/aes.js new file mode 100644 index 000000000..ce1cac024 --- /dev/null +++ b/test/spec/util/aes.js @@ -0,0 +1,50 @@ +describe('iD.utilAes', function() { + + it('encrypting and decrypting nothing yields nothing', function() { + expect(iD.utilAesEncrypt('')).to.eql(''); + expect(iD.utilAesDecrypt('')).to.eql(''); + }); + + it('encrypts and decrypts with default key', function() { + var text = 'Hello iD!'; + var encrypted = '5597506f958c68543c'; + expect(iD.utilAesEncrypt(text)).to.eql(encrypted); + expect(iD.utilAesDecrypt(encrypted)).to.eql(text); + }); + + it('encrypts and decrypts with a custom 16-bit key', function() { + var key = [ + 216, 159, 213, 140, 129, 75, 80, 121, + 67, 201, 179, 120, 71, 237, 185, 42 + ]; + var text = 'Hello iD!'; + var encrypted = '9ff50e32b04f86640a'; + expect(iD.utilAesEncrypt(text, key)).to.eql(encrypted); + expect(iD.utilAesDecrypt(encrypted, key)).to.eql(text); + }); + + it('encrypts and decrypts with a custom 24-bit key', function() { + var key = [ + 180, 138, 124, 87, 157, 23, 209, 147, + 64, 65, 68, 206, 212, 79, 215, 114, + 37, 18, 159, 94, 168, 68, 177, 202 + ]; + var text = 'Hello iD!'; + var encrypted = '85fc05011fa7848417'; + expect(iD.utilAesEncrypt(text, key)).to.eql(encrypted); + expect(iD.utilAesDecrypt(encrypted, key)).to.eql(text); + }); + + it('encrypts and decrypts with a custom 32-bit key', function() { + var key = [ + 4, 48, 130, 253, 213, 139, 96, 178, + 170, 108, 127, 233, 167, 137, 181, 41, + 145, 62, 251, 9, 82, 159, 103, 198, + 63, 200, 158, 104, 188, 77, 193, 16 + ]; + var text = 'Hello iD!'; + var encrypted = '13c21d3dc25165c57c'; + expect(iD.utilAesEncrypt(text, key)).to.eql(encrypted); + expect(iD.utilAesDecrypt(encrypted, key)).to.eql(text); + }); +});