crypto.test.js 13 KB

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