crypto.test.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. import blake2bReference from 'blake2b';
  2. import _sodium from 'libsodium-wrappers';
  3. import tweetNaCl from 'tweetnacl';
  4. import {
  5. argon2id,
  6. blake2bMac256,
  7. hmacSha256,
  8. scrypt,
  9. sha256,
  10. x25519DerivePublicKey,
  11. x25519HSalsa20DeriveSharedSecret,
  12. xChaCha20Poly1305Decrypt,
  13. xChaCha20Poly1305Encrypt,
  14. xSalsa20Poly1305Decrypt,
  15. xSalsa20Poly1305Encrypt,
  16. } from '../../build/wasm/nodejs/libthreema.js';
  17. import {assert, evaluateTestResults, init, runTest} from './utils.js';
  18. // Test SHA-256 against test vectors
  19. function testSha256() {
  20. // Test vectors taken from https://www.di-mgt.com.au/sha_testvectors.html
  21. const testVectors = [
  22. {
  23. data: '',
  24. expected:
  25. 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
  26. },
  27. {
  28. data: 'abc',
  29. expected:
  30. 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad',
  31. },
  32. {
  33. data: 'a'.repeat(1_000_000),
  34. expected:
  35. 'cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0',
  36. },
  37. ];
  38. for (const testVector of testVectors) {
  39. const computed = Buffer.from(
  40. sha256(Buffer.from(testVector.data, 'utf8')),
  41. ).toString('hex');
  42. assert(
  43. testVector.expected === computed,
  44. `Computed SHA-256 should match expected one. Input: ${testVector.data}`,
  45. );
  46. }
  47. }
  48. // Test HMAC-SHA-256 against test vectors
  49. function testHmacSha256() {
  50. // Test vectors taken from https://datatracker.ietf.org/doc/html/rfc4231#section-4
  51. const testVectors = [
  52. {
  53. key: '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b',
  54. data: 'Hi There',
  55. expected:
  56. 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7',
  57. },
  58. {
  59. key: '4a656665',
  60. data: 'what do ya want for nothing?',
  61. expected:
  62. '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843',
  63. },
  64. {
  65. key: 'a'.repeat(262),
  66. data: 'Test Using Larger Than Block-Size Key - Hash Key First',
  67. expected:
  68. '60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54',
  69. },
  70. ];
  71. for (const testVector of testVectors) {
  72. const computed = Buffer.from(
  73. hmacSha256(
  74. Buffer.from(testVector.key, 'hex'),
  75. Buffer.from(testVector.data, 'utf8'),
  76. ),
  77. ).toString('hex');
  78. assert(
  79. testVector.expected === computed,
  80. `Computed HMAC-SHA-256 should match expected one. Input: ${testVector.data}, Key: ${testVector.key}`,
  81. );
  82. }
  83. }
  84. // Test Argon2id against test vectors
  85. function testArgon2id() {
  86. // Test vector taken from
  87. // https://github.com/RustCrypto/password-hashes/blob/master/argon2/tests/kat.rs
  88. const argon2idParameters = {
  89. memoryCost: 32,
  90. timeCost: 3,
  91. parallelism: 4,
  92. outputLength: 32,
  93. };
  94. const computed = Buffer.from(
  95. argon2id(
  96. Buffer.from('01'.repeat(32), 'hex'),
  97. Buffer.from('02'.repeat(16), 'hex'),
  98. argon2idParameters,
  99. ),
  100. ).toString('hex');
  101. assert(
  102. '03aab965c12001c9d7d0d2de33192c0494b684bb148196d73c1df1acaf6d0c2e' ===
  103. computed,
  104. 'Computed Argon2id hash should match expected one. ',
  105. );
  106. }
  107. // Test Scrypt against test vectors
  108. function testScrypt() {
  109. // Test vector taken from the original paper (https://www.tarsnap.com/scrypt/scrypt.pdf)
  110. const scryptParameters = {
  111. logMemoryCost: 10,
  112. blockSize: 8,
  113. parallelism: 16,
  114. outputLength: 64,
  115. };
  116. const computed = Buffer.from(
  117. scrypt(
  118. Buffer.from('password', 'utf8'),
  119. Buffer.from('NaCl', 'utf8'),
  120. scryptParameters,
  121. ),
  122. ).toString('hex');
  123. assert(
  124. 'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640' ===
  125. computed,
  126. 'Computed Scrypt hash should match expected one',
  127. );
  128. }
  129. // Test BLake2b against test vectors
  130. function testBlake2b() {
  131. const outputLength = 32;
  132. const testVectors = [
  133. {key: undefined, salt: undefined, personal: undefined},
  134. {key: undefined, salt: '01'.repeat(16), personal: undefined},
  135. {key: undefined, salt: '02'.repeat(16), personal: 'cd'.repeat(16)},
  136. {key: 'hello my fancy key', salt: undefined, personal: undefined},
  137. {
  138. key: 'hello dear personal',
  139. salt: '03'.repeat(16),
  140. personal: 'ab'.repeat(16),
  141. },
  142. {
  143. key: 'hello my dear salt',
  144. salt: '04'.repeat(16),
  145. personal: 'ef'.repeat(16),
  146. },
  147. ];
  148. for (const testVector of testVectors) {
  149. const key =
  150. testVector.key === undefined
  151. ? undefined
  152. : Buffer.from(testVector.key, 'utf8');
  153. const salt =
  154. testVector.salt === undefined
  155. ? undefined
  156. : Buffer.from(testVector.salt, 'hex');
  157. const personal =
  158. testVector.personal === undefined
  159. ? undefined
  160. : Buffer.from(testVector.personal, 'hex');
  161. const computed = Buffer.from(
  162. blake2bMac256(
  163. key,
  164. personal ?? new Uint8Array(0),
  165. salt ?? new Uint8Array(0),
  166. ),
  167. ).toString('hex');
  168. // Dispatch to the correct function call depending on the existance of
  169. // salt and personal
  170. const expected = blake2bReference(
  171. outputLength,
  172. key,
  173. salt,
  174. personal,
  175. true,
  176. ).digest('hex');
  177. assert(
  178. expected === computed,
  179. `Computed Blake2b should match expected one. Key: ${testVector.key}, Salt: ${testVector.salt}, Personal: ${testVector.personal}`,
  180. );
  181. }
  182. }
  183. // Test XSalsa20Poly1305 by encrypting/decrypting between libthreema and TweetNacl
  184. function testXSalsa20Poly1305() {
  185. // Compute Keys
  186. // Alice uses libthreema
  187. const aliceSecretKey = Buffer.from('aa'.repeat(32), 'hex');
  188. const alicePublicKey = x25519DerivePublicKey(aliceSecretKey);
  189. // Bob uses tweetNaCl
  190. const bobSecretKey = Buffer.from('bb'.repeat(32), 'hex');
  191. const bobPublicKey =
  192. tweetNaCl.box.keyPair.fromSecretKey(bobSecretKey).publicKey;
  193. // Derive shared keys between Alice and Bob
  194. const sharedKeyAliceBob = x25519HSalsa20DeriveSharedSecret(
  195. bobPublicKey,
  196. aliceSecretKey,
  197. );
  198. const sharedKeyBobAlice = tweetNaCl.box.before(
  199. alicePublicKey,
  200. bobSecretKey,
  201. );
  202. // Encrypt from Alice to Bob
  203. const nonceAliceBob = Buffer.from('01'.repeat(24), 'hex');
  204. const messageAliceBob = 'Hi Bob';
  205. const encryptedMessageAliceBob = xSalsa20Poly1305Encrypt(
  206. sharedKeyAliceBob,
  207. nonceAliceBob,
  208. Buffer.from(messageAliceBob, 'utf8'),
  209. [],
  210. );
  211. // Decrypt
  212. const decryptedMessageAliceBob = Buffer.from(
  213. tweetNaCl.box.open.after(
  214. encryptedMessageAliceBob,
  215. nonceAliceBob,
  216. sharedKeyBobAlice,
  217. ),
  218. ).toString('utf8');
  219. assert(
  220. decryptedMessageAliceBob === messageAliceBob,
  221. 'Decrypted message from Alice to Bob does not match original one',
  222. );
  223. // Encrypt from Bob to Alice
  224. const nonceBobAlice = Buffer.from('02'.repeat(24), 'hex');
  225. const messageBobAlice = 'Hi Alice from Bob';
  226. const encryptedMessageBobAlice = tweetNaCl.box.after(
  227. Buffer.from(messageBobAlice, 'utf8'),
  228. nonceBobAlice,
  229. sharedKeyBobAlice,
  230. );
  231. // Decrypt
  232. const decryptedMessageBobAlice = Buffer.from(
  233. xSalsa20Poly1305Decrypt(
  234. sharedKeyAliceBob,
  235. nonceBobAlice,
  236. encryptedMessageBobAlice,
  237. ),
  238. ).toString('utf8');
  239. assert(
  240. decryptedMessageBobAlice === messageBobAlice,
  241. 'Decrypted message from Bob to Alice does not match original one',
  242. );
  243. }
  244. async function testXChaCha20Poly1305() {
  245. await _sodium.ready;
  246. const sodium = _sodium;
  247. // Compute Keys
  248. // Alice uses libthreema
  249. const aliceSecretKey = Buffer.from('aa'.repeat(32, 'hex'));
  250. const alicePublicKey = x25519DerivePublicKey(aliceSecretKey);
  251. // Bob uses libsodium
  252. const bobKeys = sodium.crypto_box_seed_keypair(
  253. Buffer.from('bb'.repeat(32, 'hex')),
  254. );
  255. const bobSecretKey = bobKeys.privateKey;
  256. const bobPublicKey = bobKeys.publicKey;
  257. // Derive shared keys between Alice and Bob
  258. const sharedKeyAliceBob = x25519HSalsa20DeriveSharedSecret(
  259. bobPublicKey,
  260. aliceSecretKey,
  261. );
  262. const sharedKeyBobAlice = sodium.crypto_box_beforenm(
  263. alicePublicKey,
  264. bobSecretKey,
  265. );
  266. assert(
  267. Buffer.from(sharedKeyAliceBob).toString('hex') ===
  268. Buffer.from(sharedKeyBobAlice).toString('hex'),
  269. 'Derived Keys do not match.',
  270. );
  271. // Encrypt from Alice to Bob
  272. const nonceAliceBob = Buffer.from('01'.repeat(24, 'hex'));
  273. const messageAliceBob = 'Hi Bob';
  274. const encryptedMessageAliceBob = xChaCha20Poly1305Encrypt(
  275. sharedKeyAliceBob,
  276. nonceAliceBob,
  277. Buffer.from(messageAliceBob, 'utf8'),
  278. [],
  279. );
  280. // Decrypt
  281. const decryptedMessageAliceBob = Buffer.from(
  282. sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
  283. null,
  284. encryptedMessageAliceBob,
  285. null,
  286. nonceAliceBob,
  287. sharedKeyBobAlice,
  288. ),
  289. ).toString('utf8');
  290. assert(
  291. decryptedMessageAliceBob === messageAliceBob,
  292. 'Decrypted message from Alice to Bob does not match original one',
  293. );
  294. // Encrypt from Bob to Alice
  295. const nonceBobAlice = Buffer.from('02'.repeat(24, 'hex'));
  296. const messageBobAlice = 'Hi Alice from Bob';
  297. const encryptedMessageBobAlice =
  298. sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
  299. Buffer.from(messageBobAlice, 'utf8'),
  300. null,
  301. null,
  302. nonceBobAlice,
  303. sharedKeyBobAlice,
  304. );
  305. // Decrypt
  306. const decryptedMessageBobAlice = Buffer.from(
  307. xChaCha20Poly1305Decrypt(
  308. sharedKeyAliceBob,
  309. nonceBobAlice,
  310. encryptedMessageBobAlice,
  311. [],
  312. ),
  313. ).toString('utf8');
  314. assert(
  315. decryptedMessageBobAlice === messageBobAlice,
  316. 'Decrypted message from Bob to Alice does not match original one',
  317. );
  318. }
  319. // Initialize libthreema
  320. init();
  321. // Run tests
  322. evaluateTestResults([
  323. runTest(testSha256, 'SHA-256'),
  324. runTest(testHmacSha256, 'HMAC-SHA-256'),
  325. runTest(testArgon2id, 'Argon2id'),
  326. runTest(testScrypt, 'Scrypt'),
  327. runTest(testBlake2b, 'Blake2b'),
  328. runTest(testXSalsa20Poly1305, 'XSalsa20Poly1305'),
  329. runTest(testXChaCha20Poly1305, 'XChaCha20Poly1305'),
  330. ]);