symmetric.d 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. module secured.symmetric;
  2. import std.base64;
  3. import std.conv;
  4. import std.stdio;
  5. import std.string;
  6. import deimos.openssl.evp;
  7. import secured.hash;
  8. import secured.mac;
  9. import secured.kdf;
  10. import secured.random;
  11. import secured.util;
  12. import secured.openssl;
  13. public enum SymmetricAlgorithm : ubyte {
  14. AES128_GCM,
  15. AES128_CTR,
  16. AES128_CFB,
  17. AES128_CBC,
  18. AES192_GCM,
  19. AES192_CTR,
  20. AES192_CFB,
  21. AES192_CBC,
  22. AES256_GCM,
  23. AES256_CTR,
  24. AES256_CFB,
  25. AES256_CBC,
  26. ChaCha20,
  27. ChaCha20_Poly1305,
  28. Default = AES256_GCM,
  29. }
  30. public immutable struct EncryptedData {
  31. public immutable ubyte[] iv;
  32. public immutable ubyte[] cipherText;
  33. public immutable ubyte[] authTag;
  34. private immutable SymmetricAlgorithm algorithm;
  35. private immutable HashAlgorithm hashAlgorithm;
  36. @safe public this(const string encoded, SymmetricAlgorithm algorithm = SymmetricAlgorithm.Default, HashAlgorithm hashAlgorithm = HashAlgorithm.Default) {
  37. this.algorithm = algorithm;
  38. this.hashAlgorithm = hashAlgorithm;
  39. this(Base64.decode(encoded), getCipherIVLength(algorithm), getAuthLength(algorithm, hashAlgorithm), algorithm, hashAlgorithm);
  40. }
  41. @trusted public this(const ubyte[] rawCiphertext, size_t ivLength, size_t authTagLength, SymmetricAlgorithm algorithm = SymmetricAlgorithm.Default, HashAlgorithm hashAlgorithm = HashAlgorithm.Default) {
  42. if (rawCiphertext.length <= ivLength + authTagLength)
  43. throw new CryptographicException("Incorrect ciphertext length");
  44. this.algorithm = algorithm;
  45. this.hashAlgorithm = hashAlgorithm;
  46. this.iv = cast(immutable) rawCiphertext[0 .. ivLength];
  47. this.cipherText = cast(immutable) rawCiphertext[ivLength .. $-authTagLength];
  48. this.authTag = cast(immutable) rawCiphertext[$-authTagLength .. $];
  49. }
  50. @trusted public this(const ubyte[] cipherText, const ubyte[] iv, const ubyte[] authTag, SymmetricAlgorithm algorithm = SymmetricAlgorithm.Default, HashAlgorithm hashAlgorithm = HashAlgorithm.Default) {
  51. this.iv = cast(immutable) iv;
  52. this.cipherText = cast(immutable) cipherText;
  53. this.authTag = cast(immutable) authTag;
  54. this.algorithm = algorithm;
  55. this.hashAlgorithm = hashAlgorithm;
  56. }
  57. public string toString() {
  58. return to!string(Base64.encode(iv ~ cipherText ~ authTag));
  59. }
  60. }
  61. public struct SymmetricKey {
  62. package ubyte[] value;
  63. package SymmetricAlgorithm algorithm;
  64. @disable this();
  65. public @property ubyte[] key() { return value; }
  66. public string toString() {
  67. return Base64.encode(value);
  68. }
  69. }
  70. public struct SymmetricKeyIV{
  71. ubyte[] key;
  72. ubyte[] iv;
  73. SymmetricAlgorithm algorithm;
  74. }
  75. @safe public SymmetricKeyIV generateSymmetricKeyIV(SymmetricAlgorithm algorithm = SymmetricAlgorithm.AES256_CBC){
  76. SymmetricKeyIV KeyIV = { key : random(getCipherKeyLength(algorithm)), iv : random(getCipherIVLength(algorithm)), algorithm: algorithm };
  77. return KeyIV;
  78. }
  79. @safe public SymmetricKey generateSymmetricKey(SymmetricAlgorithm algorithm = SymmetricAlgorithm.Default) {
  80. SymmetricKey key = SymmetricKey.init;
  81. key.value = random(getCipherKeyLength(algorithm));
  82. key.algorithm = algorithm;
  83. return key;
  84. }
  85. @safe public SymmetricKey generateSymmetricKey(const string password, SymmetricAlgorithm algorithm = SymmetricAlgorithm.Default, KdfAlgorithm kdf = KdfAlgorithm.Default) {
  86. SymmetricKey key = SymmetricKey.init;
  87. key.algorithm = algorithm;
  88. if (kdf == KdfAlgorithm.SCrypt) {
  89. key.value = scrypt_ex(password, null, getCipherKeyLength(algorithm));
  90. } else if (kdf == KdfAlgorithm.PBKDF2) {
  91. key.value = pbkdf2_ex(password, null, HashAlgorithm.Default, getCipherKeyLength(algorithm), defaultKdfIterations);
  92. } else {
  93. throw new CryptographicException("Specified KDF '" ~ to!string(kdf) ~ "' is not supported.");
  94. }
  95. return key;
  96. }
  97. @trusted public SymmetricKey initializeSymmetricKey(const ubyte[] bytes, SymmetricAlgorithm algorithm = SymmetricAlgorithm.Default) {
  98. if (bytes.length != (getCipherKeyLength(algorithm))) {
  99. throw new CryptographicException("Encryption Key must be " ~ to!string(getCipherKeyLength(algorithm)) ~ " bytes in length.");
  100. }
  101. SymmetricKey key = SymmetricKey.init;
  102. key.value = cast(ubyte[])bytes;
  103. key.algorithm = algorithm;
  104. return key;
  105. }
  106. pragma(inline) @safe private ubyte[] deriveKey(const ubyte[] key, uint bytes, const ubyte[] salt, HashAlgorithm hash = HashAlgorithm.Default) {
  107. return hkdf_ex(key, salt, string.init, bytes, hash);
  108. }
  109. @safe public EncryptedData encrypt(const ubyte[] key, const ubyte[] iv, const ubyte[] data, SymmetricAlgorithm algorithm){
  110. ubyte[] authTag = [];
  111. const ubyte[] associatedData = null;
  112. ubyte[] result = encrypt_ex(data, associatedData, key, iv, authTag, algorithm);
  113. return EncryptedData(result, iv, authTag, algorithm);
  114. }
  115. @safe public EncryptedData encrypt(const SymmetricKey key, const ubyte[] data, const ubyte[] associatedData = null) {
  116. ubyte[] iv = random(getCipherIVLength(key.algorithm));
  117. ubyte[] derived = deriveKey(key.value, getCipherKeyLength(key.algorithm), iv);
  118. ubyte[] authTag;
  119. ubyte[] result = encrypt_ex(data, associatedData, derived, iv, authTag, key.algorithm);
  120. return EncryptedData(result, iv, authTag, key.algorithm);
  121. }
  122. @trusted public ubyte[] encrypt_ex(const ubyte[] data, const ubyte[] associatedData, const ubyte[] encryptionKey, const ubyte[] iv, out ubyte[] authTag, SymmetricAlgorithm algorithm) {
  123. if (encryptionKey.length != getCipherKeyLength(algorithm)) {
  124. throw new CryptographicException("Encryption Key must be " ~ to!string(getCipherKeyLength(algorithm)) ~ " bytes in length.");
  125. }
  126. if (iv.length != getCipherIVLength(algorithm)) {
  127. throw new CryptographicException("IV must be " ~ to!string(getCipherIVLength(algorithm)) ~ " bytes in length.");
  128. }
  129. //Get the OpenSSL cipher context
  130. EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
  131. if (ctx is null) {
  132. throw new CryptographicException("Cannot get an OpenSSL cipher context.");
  133. }
  134. scope(exit) {
  135. if (ctx !is null) {
  136. EVP_CIPHER_CTX_free(ctx);
  137. }
  138. }
  139. //Initialize the cipher context
  140. if (EVP_EncryptInit_ex(ctx, getOpenSslCipher(algorithm), null, encryptionKey.ptr, iv.ptr) != 1) {
  141. throw new CryptographicException("Cannot initialize the OpenSSL cipher context.");
  142. }
  143. //Write the additional data to the cipher context, if any
  144. if (associatedData !is null && isAeadCipher(algorithm)) {
  145. int aadLen = 0;
  146. if (EVP_EncryptUpdate(ctx, null, &aadLen, associatedData.ptr, cast(int)associatedData.length) != 1) {
  147. throw new CryptographicException("Unable to write bytes to cipher context.");
  148. }
  149. }
  150. //Write data to the cipher context
  151. int written = 0;
  152. int len = 0;
  153. ubyte[] output = new ubyte[data.length + 32];
  154. if (EVP_EncryptUpdate(ctx, &output[written], &len, data.ptr, cast(int)data.length) != 1) {
  155. throw new CryptographicException("Unable to write bytes to cipher context.");
  156. }
  157. written += len;
  158. //Extract the complete ciphertext
  159. if (EVP_EncryptFinal_ex(ctx, &output[written], &len) != 1) {
  160. throw new CryptographicException("Unable to extract the ciphertext from the cipher context.");
  161. }
  162. written += len;
  163. ubyte[] result = output[0..written];
  164. //Extract the auth tag
  165. if (isAeadCipher(algorithm)) {
  166. ubyte[] _auth = new ubyte[getAuthLength(algorithm)];
  167. if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, getAuthLength(algorithm), _auth.ptr) != 1) {
  168. throw new CryptographicException("Unable to extract the authentication tag from the cipher context.");
  169. }
  170. authTag = _auth;
  171. } else {
  172. authTag = hmac(iv, hash(result) ~ hash(associatedData));
  173. }
  174. return result;
  175. }
  176. @safe public ubyte[] decrypt(const ubyte[] key, const ubyte[] iv, const ubyte[] data, SymmetricAlgorithm algorithm){
  177. const ubyte[] associatedData = [];
  178. const ubyte[] authTag = [];
  179. return decrypt_ex(data, associatedData, key, iv, authTag, algorithm);
  180. }
  181. @safe public ubyte[] decrypt(const SymmetricKey key, const EncryptedData data, const ubyte[] associatedData = null) {
  182. if (data.algorithm != key.algorithm)
  183. throw new CryptographicException("Key and data algorithms don't match");
  184. ubyte[] derived = deriveKey(key.value, getCipherKeyLength(key.algorithm), data.iv);
  185. return decrypt_ex(data.cipherText, associatedData, derived, data.iv, data.authTag, key.algorithm);
  186. }
  187. @trusted public ubyte[] decrypt_ex(const ubyte[] data, const ubyte[] associatedData, const ubyte[] encryptionKey, const ubyte[] iv, const ubyte[] authTag, SymmetricAlgorithm algorithm) {
  188. if (encryptionKey.length != getCipherKeyLength(algorithm)) {
  189. throw new CryptographicException("Encryption Key must be " ~ to!string(getCipherKeyLength(algorithm)) ~ " bytes in length.");
  190. }
  191. if (iv.length != getCipherIVLength(algorithm)) {
  192. throw new CryptographicException("IV must be " ~ to!string(getCipherIVLength(algorithm)) ~ " bytes in length.");
  193. }
  194. if(!isAeadCipher(algorithm)){
  195. // do not make HMAC check for not-AEAD (like AES-CBC)
  196. }else{
  197. if(!hmac_verify(authTag, iv, hash(data) ~ hash(associatedData))){
  198. throw new CryptographicException("Failed to verify the authTag.");
  199. }
  200. }
  201. //Get the OpenSSL cipher context
  202. EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
  203. if (ctx is null) {
  204. throw new CryptographicException("Cannot get an OpenSSL cipher context.");
  205. }
  206. scope(exit) {
  207. if (ctx !is null) {
  208. EVP_CIPHER_CTX_free(ctx);
  209. }
  210. }
  211. //Initialize the cipher context
  212. if (!EVP_DecryptInit_ex(ctx, getOpenSslCipher(algorithm), null, encryptionKey.ptr, iv.ptr)) {
  213. throw new CryptographicException("Cannot initialize the OpenSSL cipher context.");
  214. }
  215. //Write the additional data to the cipher context, if any
  216. if (associatedData.length != 0 && isAeadCipher(algorithm)) {
  217. int aadLen = 0;
  218. if (!EVP_DecryptUpdate(ctx, null, &aadLen, associatedData.ptr, cast(int)associatedData.length)) {
  219. throw new CryptographicException("Unable to write bytes to cipher context.");
  220. }
  221. }
  222. //Use the supplied tag to verify the message
  223. if (isAeadCipher(algorithm)) {
  224. if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, cast(int)authTag.length, (cast(ubyte[])authTag).ptr)) {
  225. throw new CryptographicException("Unable to set the authentication tag on the cipher context.");
  226. }
  227. }
  228. //Write data to the cipher context
  229. int written = 0;
  230. int len = 0;
  231. ubyte[] output = new ubyte[data.length + 32];
  232. if (!EVP_DecryptUpdate(ctx, &output[written], &len, data.ptr, cast(int)data.length)) {
  233. throw new CryptographicException("Unable to write bytes to cipher context.");
  234. }
  235. written += len;
  236. //Extract the complete plaintext
  237. if (EVP_DecryptFinal_ex(ctx, &output[written], &len) <= 0) {
  238. throw new CryptographicException("Unable to extract the plaintext from the cipher context.");
  239. }
  240. written += len;
  241. return output[0..written];
  242. }
  243. @trusted package const(EVP_CIPHER*) getOpenSslCipher(SymmetricAlgorithm algo) {
  244. switch(algo) {
  245. case SymmetricAlgorithm.AES128_GCM: return EVP_aes_128_gcm();
  246. case SymmetricAlgorithm.AES192_GCM: return EVP_aes_192_gcm();
  247. case SymmetricAlgorithm.AES256_GCM: return EVP_aes_256_gcm();
  248. case SymmetricAlgorithm.AES128_CTR: return EVP_aes_128_ctr();
  249. case SymmetricAlgorithm.AES192_CTR: return EVP_aes_192_ctr();
  250. case SymmetricAlgorithm.AES256_CTR: return EVP_aes_256_ctr();
  251. case SymmetricAlgorithm.AES128_CFB: return EVP_aes_128_cfb();
  252. case SymmetricAlgorithm.AES192_CFB: return EVP_aes_192_cfb();
  253. case SymmetricAlgorithm.AES256_CFB: return EVP_aes_256_cfb();
  254. case SymmetricAlgorithm.AES128_CBC: return EVP_aes_128_cbc();
  255. case SymmetricAlgorithm.AES192_CBC: return EVP_aes_192_cbc();
  256. case SymmetricAlgorithm.AES256_CBC: return EVP_aes_256_cbc();
  257. case SymmetricAlgorithm.ChaCha20: return EVP_chacha20();
  258. case SymmetricAlgorithm.ChaCha20_Poly1305: return EVP_chacha20_poly1305();
  259. default: return EVP_aes_256_gcm();
  260. }
  261. }
  262. @safe package bool isAeadCipher(SymmetricAlgorithm algo) {
  263. switch(algo) {
  264. case SymmetricAlgorithm.AES128_GCM: return true;
  265. case SymmetricAlgorithm.AES192_GCM: return true;
  266. case SymmetricAlgorithm.AES256_GCM: return true;
  267. case SymmetricAlgorithm.ChaCha20_Poly1305: return true;
  268. default: return false;
  269. }
  270. }
  271. @safe package uint getCipherKeyLength(SymmetricAlgorithm algo) {
  272. switch(algo) {
  273. case SymmetricAlgorithm.AES128_GCM: return 16;
  274. case SymmetricAlgorithm.AES192_GCM: return 24;
  275. case SymmetricAlgorithm.AES256_GCM: return 32;
  276. case SymmetricAlgorithm.AES128_CTR: return 16;
  277. case SymmetricAlgorithm.AES192_CTR: return 24;
  278. case SymmetricAlgorithm.AES256_CTR: return 32;
  279. case SymmetricAlgorithm.AES128_CFB: return 16;
  280. case SymmetricAlgorithm.AES192_CFB: return 24;
  281. case SymmetricAlgorithm.AES256_CFB: return 32;
  282. case SymmetricAlgorithm.AES128_CBC: return 16;
  283. case SymmetricAlgorithm.AES192_CBC: return 24;
  284. case SymmetricAlgorithm.AES256_CBC: return 32;
  285. case SymmetricAlgorithm.ChaCha20: return 32;
  286. case SymmetricAlgorithm.ChaCha20_Poly1305: return 32;
  287. default: return 16;
  288. }
  289. }
  290. @safe package uint getCipherIVLength(SymmetricAlgorithm algo) {
  291. switch(algo) {
  292. case SymmetricAlgorithm.AES128_GCM: return 12;
  293. case SymmetricAlgorithm.AES192_GCM: return 12;
  294. case SymmetricAlgorithm.AES256_GCM: return 12;
  295. case SymmetricAlgorithm.ChaCha20: return 12;
  296. case SymmetricAlgorithm.ChaCha20_Poly1305: return 12;
  297. default: return 16;
  298. }
  299. }
  300. @safe package uint getAuthLength(SymmetricAlgorithm symmetric, HashAlgorithm hash = HashAlgorithm.Default) {
  301. switch(symmetric) {
  302. case SymmetricAlgorithm.AES128_GCM: return 16;
  303. case SymmetricAlgorithm.AES192_GCM: return 16;
  304. case SymmetricAlgorithm.AES256_GCM: return 16;
  305. case SymmetricAlgorithm.ChaCha20_Poly1305: return 16;
  306. default: return getHashLength(hash);
  307. }
  308. }
  309. unittest
  310. {
  311. import std.digest;
  312. import std.stdio;
  313. ubyte[] input = cast(ubyte[])"The quick brown fox jumps over the lazy dog.";
  314. SymmetricKey key = initializeSymmetricKey([ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
  315. 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ]);
  316. SymmetricKey generateTest = generateSymmetricKey();
  317. assert(generateTest.value.length == 32);
  318. SymmetricKey passwordTest = generateSymmetricKey("Test Password");
  319. writeln("Password Key: ", toHexString!(LetterCase.lower)(passwordTest.value));
  320. assert(toHexString!(LetterCase.lower)(passwordTest.value) == "76ae6c580be5e707a5cef313d2161899cd596c8c635671c9904602f8312cca34");
  321. writeln("Testing Encryption (No Additional Data)");
  322. writeln("Encryption Input: ", cast(string)input);
  323. EncryptedData enc = encrypt(key, input);
  324. writeln("Encryption Output: ", toHexString!(LetterCase.lower)(enc.cipherText));
  325. writeln("AuthTag: ", toHexString!(LetterCase.lower)(enc.authTag));
  326. writeln("Testing Decryption (No Additional Data)");
  327. writeln("Decryption Input: ", toHexString!(LetterCase.lower)(enc.cipherText));
  328. ubyte[] dec = decrypt(key, enc);
  329. writeln("Decryption Output: ", cast(string)dec);
  330. assert((cast(string)dec) == cast(string)input);
  331. string encoded = enc.toString();
  332. writeln("Base64 Encoded: ", encoded);
  333. EncryptedData test = EncryptedData(encoded);
  334. ubyte[] eddec = decrypt(key, test);
  335. writeln("Decryption Output: ", cast(string)eddec);
  336. assert((cast(string)eddec) == cast(string)input);
  337. ubyte[] ad = cast(ubyte[])"Associated Data";
  338. writeln("Testing Encryption (With Additional Data)");
  339. writeln("Encryption Input: ", cast(string)input);
  340. writeln("Encryption AD: ", cast(string)ad);
  341. EncryptedData enc2 = encrypt(key, input, ad);
  342. writeln("Encryption Output: ", toHexString!(LetterCase.lower)(enc2.cipherText));
  343. writeln("AuthTag: ", toHexString!(LetterCase.lower)(enc2.authTag));
  344. writeln("Testing Decryption (With Additional Data)");
  345. writeln("Decryption Input: ", toHexString!(LetterCase.lower)(enc2.cipherText));
  346. writeln("Decryption AD: ", cast(string)ad);
  347. ubyte[] dec2 = decrypt(key, enc2, ad);
  348. writeln("Decryption Output: ", cast(string)dec2);
  349. assert((cast(string)dec2) == cast(string)input);
  350. writeln("Testing Non-AEAD Encryption (With Additional Data)");
  351. writeln("Encryption Input: ", cast(string)input);
  352. writeln("Encryption AD: ", cast(string)ad);
  353. SymmetricKey nonAeadKey = generateSymmetricKey(SymmetricAlgorithm.AES256_CBC);
  354. EncryptedData enc3 = encrypt(nonAeadKey, input, ad);
  355. writeln("Encryption Output: ", enc3);
  356. writeln("IV: ", toHexString!(LetterCase.lower)(enc3.iv));
  357. writeln("AuthTag: ", toHexString!(LetterCase.lower)(enc3.authTag));
  358. writeln("Testing Non-AEAD Decryption (With Additional Data)");
  359. writeln("Decryption Input: ", enc3);
  360. writeln("Decryption AD: ", cast(string)ad);
  361. ubyte[] dec3 = decrypt(nonAeadKey, enc3, ad);
  362. writeln("Decryption Output: ", cast(string)dec3);
  363. assert((cast(string)dec3) == cast(string)input);
  364. }