cyberchef 11.2.0 → 11.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/AGENTS.md +75 -0
  2. package/CHANGELOG.md +147 -1
  3. package/Dockerfile +2 -2
  4. package/Gruntfile.js +1 -0
  5. package/package.json +22 -21
  6. package/src/core/Dish.mjs +1 -5
  7. package/src/core/Ingredient.mjs +92 -0
  8. package/src/core/Operation.mjs +19 -0
  9. package/src/core/Recipe.mjs +4 -0
  10. package/src/core/Utils.mjs +48 -0
  11. package/src/core/config/Categories.json +22 -6
  12. package/src/core/config/OperationConfig.json +4771 -1226
  13. package/src/core/config/modules/Ciphers.mjs +20 -0
  14. package/src/core/config/modules/Crypto.mjs +10 -0
  15. package/src/core/config/modules/Default.mjs +8 -0
  16. package/src/core/config/modules/File.mjs +16 -0
  17. package/src/core/config/modules/OpModules.mjs +2 -0
  18. package/src/core/config/scripts/generateHTMLEntities.mjs +139 -0
  19. package/src/core/config/scripts/htmlEntityOverrides.mjs +86 -0
  20. package/src/core/lib/Arithmetic.mjs +21 -5
  21. package/src/core/lib/BigIntUtils.mjs +0 -1
  22. package/src/core/lib/COBS.mjs +86 -0
  23. package/src/core/lib/Decimal.mjs +5 -5
  24. package/src/core/lib/HTMLEntities.mjs +2068 -0
  25. package/src/core/lib/Present.mjs +422 -0
  26. package/src/core/lib/TEA.mjs +494 -0
  27. package/src/core/lib/TLVParser.mjs +16 -7
  28. package/src/core/lib/Twofish.mjs +608 -0
  29. package/src/core/operations/AsconDecrypt.mjs +112 -0
  30. package/src/core/operations/AsconEncrypt.mjs +108 -0
  31. package/src/core/operations/AsconHash.mjs +49 -0
  32. package/src/core/operations/AsconMAC.mjs +68 -0
  33. package/src/core/operations/AutomatedValidationTestOp.mjs +84 -0
  34. package/src/core/operations/AvroToJSON.mjs +1 -1
  35. package/src/core/operations/BLAKE3.mjs +5 -1
  36. package/src/core/operations/BcryptCompare.mjs +11 -5
  37. package/src/core/operations/BitShiftLeft.mjs +4 -1
  38. package/src/core/operations/Bzip2Compress.mjs +1 -1
  39. package/src/core/operations/DechunkHTTPResponse.mjs +4 -1
  40. package/src/core/operations/ExtendedGCD.mjs +101 -0
  41. package/src/core/operations/FromBase.mjs +2 -1
  42. package/src/core/operations/FromCOBS.mjs +38 -0
  43. package/src/core/operations/FromDecimal.mjs +6 -1
  44. package/src/core/operations/FromHTMLEntity.mjs +2 -1458
  45. package/src/core/operations/GenerateHOTP.mjs +21 -6
  46. package/src/core/operations/GenerateImage.mjs +22 -10
  47. package/src/core/operations/GeneratePrime.mjs +154 -0
  48. package/src/core/operations/GenerateTOTP.mjs +24 -7
  49. package/src/core/operations/Gzip.mjs +1 -3
  50. package/src/core/operations/Jsonata.mjs +12 -0
  51. package/src/core/operations/MIMEDecoding.mjs +1 -1
  52. package/src/core/operations/MOD.mjs +62 -0
  53. package/src/core/operations/ModularInverse.mjs +107 -0
  54. package/src/core/operations/PRESENTDecrypt.mjs +94 -0
  55. package/src/core/operations/PRESENTEncrypt.mjs +94 -0
  56. package/src/core/operations/ParseURI.mjs +18 -5
  57. package/src/core/operations/PseudoRandomNumberGenerator.mjs +2 -1
  58. package/src/core/operations/RandomPrime.mjs +154 -0
  59. package/src/core/operations/RenderPDF.mjs +100 -0
  60. package/src/core/operations/SM4Encrypt.mjs +1 -1
  61. package/src/core/operations/SetDifference.mjs +8 -1
  62. package/src/core/operations/SetIntersection.mjs +8 -1
  63. package/src/core/operations/ShowOnMap.mjs +14 -2
  64. package/src/core/operations/TEADecrypt.mjs +98 -0
  65. package/src/core/operations/TEAEncrypt.mjs +98 -0
  66. package/src/core/operations/ToBase.mjs +4 -4
  67. package/src/core/operations/ToBase32.mjs +20 -4
  68. package/src/core/operations/ToBinary.mjs +4 -1
  69. package/src/core/operations/ToCOBS.mjs +38 -0
  70. package/src/core/operations/ToHTMLEntity.mjs +7 -1430
  71. package/src/core/operations/TwofishDecrypt.mjs +94 -0
  72. package/src/core/operations/TwofishEncrypt.mjs +94 -0
  73. package/src/core/operations/URLEncode.mjs +22 -18
  74. package/src/core/operations/UnescapeUnicodeCharacters.mjs +2 -1
  75. package/src/core/operations/ViewBitPlane.mjs +9 -3
  76. package/src/core/operations/Wrap.mjs +5 -0
  77. package/src/core/operations/XORBruteForce.mjs +4 -1
  78. package/src/core/operations/XORChecksum.mjs +2 -2
  79. package/src/core/operations/XTEADecrypt.mjs +110 -0
  80. package/src/core/operations/XTEAEncrypt.mjs +110 -0
  81. package/src/core/operations/index.mjs +42 -0
  82. package/src/core/vendor/ascon.mjs +162 -0
  83. package/src/core/vendor/htmlEntities/entity.json +2233 -0
  84. package/src/core/vendor/htmlEntities/entity.txt +14 -0
  85. package/src/node/api.mjs +4 -1
  86. package/src/node/index.mjs +105 -0
  87. package/src/web/HTMLOperation.mjs +2 -1
  88. package/src/web/stylesheets/layout/_io.css +8 -0
  89. package/tests/browser/00_nightwatch.js +26 -0
  90. package/tests/browser/02_ops.js +20 -4
  91. package/tests/node/index.mjs +1 -0
  92. package/tests/node/tests/Dish.mjs +19 -0
  93. package/tests/node/tests/NodeDish.mjs +36 -0
  94. package/tests/node/tests/ToHTMLEntity.mjs +82 -0
  95. package/tests/node/tests/Utils.mjs +77 -0
  96. package/tests/node/tests/nodeApi.mjs +33 -1
  97. package/tests/node/tests/operations.mjs +26 -1
  98. package/tests/operations/index.mjs +17 -0
  99. package/tests/operations/tests/Arithmetic.mjs +33 -0
  100. package/tests/operations/tests/Ascon.mjs +501 -0
  101. package/tests/operations/tests/AutomatedValidation.mjs +154 -0
  102. package/tests/operations/tests/BLAKE3.mjs +39 -1
  103. package/tests/operations/tests/Base32.mjs +22 -0
  104. package/tests/operations/tests/COBS.mjs +476 -0
  105. package/tests/operations/tests/CharEnc.mjs +2 -2
  106. package/tests/operations/tests/DechunkHTTPResponse.mjs +66 -0
  107. package/tests/operations/tests/ExtendedGCD.mjs +78 -0
  108. package/tests/operations/tests/FromBase.mjs +66 -0
  109. package/tests/operations/tests/FromDecimal.mjs +55 -0
  110. package/tests/operations/tests/GenerateLoremIpsum.mjs +2 -2
  111. package/tests/operations/tests/Gzip.mjs +60 -0
  112. package/tests/operations/tests/HTMLEntity.mjs +126 -0
  113. package/tests/operations/tests/Hash.mjs +44 -0
  114. package/tests/operations/tests/Hexdump.mjs +1 -1
  115. package/tests/operations/tests/Image.mjs +26 -0
  116. package/tests/operations/tests/Jsonata.mjs +23 -0
  117. package/tests/operations/tests/MIMEDecoding.mjs +33 -0
  118. package/tests/operations/tests/MOD.mjs +208 -0
  119. package/tests/operations/tests/Median.mjs +33 -0
  120. package/tests/operations/tests/ModularInverse.mjs +78 -0
  121. package/tests/operations/tests/OTP.mjs +167 -2
  122. package/tests/operations/tests/PRESENT.mjs +465 -0
  123. package/tests/operations/tests/ParseTLV.mjs +41 -0
  124. package/tests/operations/tests/RenderPDF.mjs +55 -0
  125. package/tests/operations/tests/SM2.mjs +1 -1
  126. package/tests/operations/tests/SetDifference.mjs +22 -0
  127. package/tests/operations/tests/SetIntersection.mjs +23 -1
  128. package/tests/operations/tests/ShowOnMap.mjs +61 -0
  129. package/tests/operations/tests/TEA.mjs +566 -0
  130. package/tests/operations/tests/Twofish.mjs +486 -0
  131. package/tests/operations/tests/URLEncodeDecode.mjs +26 -0
  132. package/tests/operations/tests/UnescapeUnicodeCharacters.mjs +88 -0
  133. package/tests/operations/tests/Wrap.mjs +44 -0
  134. package/webpack.config.js +3 -3
@@ -0,0 +1,608 @@
1
+ /**
2
+ * Complete implementation of Twofish block cipher encryption/decryption with
3
+ * ECB, CBC, CFB, OFB, CTR block modes.
4
+ *
5
+ * Twofish was an AES finalist designed by Bruce Schneier et al.
6
+ * Reference: https://www.schneier.com/academic/twofish/
7
+ *
8
+ * @author Medjedtxm
9
+ * @copyright Crown Copyright 2026
10
+ * @license Apache-2.0
11
+ */
12
+
13
+ import OperationError from "../errors/OperationError.mjs";
14
+
15
+ /** Number of rounds */
16
+ const NROUNDS = 16;
17
+
18
+ /** Block size in bytes (128 bits) */
19
+ const BLOCKSIZE = 16;
20
+
21
+ /** Q0 permutation */
22
+ const Q0 = [
23
+ 0xa9, 0x67, 0xb3, 0xe8, 0x04, 0xfd, 0xa3, 0x76, 0x9a, 0x92, 0x80, 0x78, 0xe4, 0xdd, 0xd1, 0x38,
24
+ 0x0d, 0xc6, 0x35, 0x98, 0x18, 0xf7, 0xec, 0x6c, 0x43, 0x75, 0x37, 0x26, 0xfa, 0x13, 0x94, 0x48,
25
+ 0xf2, 0xd0, 0x8b, 0x30, 0x84, 0x54, 0xdf, 0x23, 0x19, 0x5b, 0x3d, 0x59, 0xf3, 0xae, 0xa2, 0x82,
26
+ 0x63, 0x01, 0x83, 0x2e, 0xd9, 0x51, 0x9b, 0x7c, 0xa6, 0xeb, 0xa5, 0xbe, 0x16, 0x0c, 0xe3, 0x61,
27
+ 0xc0, 0x8c, 0x3a, 0xf5, 0x73, 0x2c, 0x25, 0x0b, 0xbb, 0x4e, 0x89, 0x6b, 0x53, 0x6a, 0xb4, 0xf1,
28
+ 0xe1, 0xe6, 0xbd, 0x45, 0xe2, 0xf4, 0xb6, 0x66, 0xcc, 0x95, 0x03, 0x56, 0xd4, 0x1c, 0x1e, 0xd7,
29
+ 0xfb, 0xc3, 0x8e, 0xb5, 0xe9, 0xcf, 0xbf, 0xba, 0xea, 0x77, 0x39, 0xaf, 0x33, 0xc9, 0x62, 0x71,
30
+ 0x81, 0x79, 0x09, 0xad, 0x24, 0xcd, 0xf9, 0xd8, 0xe5, 0xc5, 0xb9, 0x4d, 0x44, 0x08, 0x86, 0xe7,
31
+ 0xa1, 0x1d, 0xaa, 0xed, 0x06, 0x70, 0xb2, 0xd2, 0x41, 0x7b, 0xa0, 0x11, 0x31, 0xc2, 0x27, 0x90,
32
+ 0x20, 0xf6, 0x60, 0xff, 0x96, 0x5c, 0xb1, 0xab, 0x9e, 0x9c, 0x52, 0x1b, 0x5f, 0x93, 0x0a, 0xef,
33
+ 0x91, 0x85, 0x49, 0xee, 0x2d, 0x4f, 0x8f, 0x3b, 0x47, 0x87, 0x6d, 0x46, 0xd6, 0x3e, 0x69, 0x64,
34
+ 0x2a, 0xce, 0xcb, 0x2f, 0xfc, 0x97, 0x05, 0x7a, 0xac, 0x7f, 0xd5, 0x1a, 0x4b, 0x0e, 0xa7, 0x5a,
35
+ 0x28, 0x14, 0x3f, 0x29, 0x88, 0x3c, 0x4c, 0x02, 0xb8, 0xda, 0xb0, 0x17, 0x55, 0x1f, 0x8a, 0x7d,
36
+ 0x57, 0xc7, 0x8d, 0x74, 0xb7, 0xc4, 0x9f, 0x72, 0x7e, 0x15, 0x22, 0x12, 0x58, 0x07, 0x99, 0x34,
37
+ 0x6e, 0x50, 0xde, 0x68, 0x65, 0xbc, 0xdb, 0xf8, 0xc8, 0xa8, 0x2b, 0x40, 0xdc, 0xfe, 0x32, 0xa4,
38
+ 0xca, 0x10, 0x21, 0xf0, 0xd3, 0x5d, 0x0f, 0x00, 0x6f, 0x9d, 0x36, 0x42, 0x4a, 0x5e, 0xc1, 0xe0
39
+ ];
40
+
41
+ /** Q1 permutation */
42
+ const Q1 = [
43
+ 0x75, 0xf3, 0xc6, 0xf4, 0xdb, 0x7b, 0xfb, 0xc8, 0x4a, 0xd3, 0xe6, 0x6b, 0x45, 0x7d, 0xe8, 0x4b,
44
+ 0xd6, 0x32, 0xd8, 0xfd, 0x37, 0x71, 0xf1, 0xe1, 0x30, 0x0f, 0xf8, 0x1b, 0x87, 0xfa, 0x06, 0x3f,
45
+ 0x5e, 0xba, 0xae, 0x5b, 0x8a, 0x00, 0xbc, 0x9d, 0x6d, 0xc1, 0xb1, 0x0e, 0x80, 0x5d, 0xd2, 0xd5,
46
+ 0xa0, 0x84, 0x07, 0x14, 0xb5, 0x90, 0x2c, 0xa3, 0xb2, 0x73, 0x4c, 0x54, 0x92, 0x74, 0x36, 0x51,
47
+ 0x38, 0xb0, 0xbd, 0x5a, 0xfc, 0x60, 0x62, 0x96, 0x6c, 0x42, 0xf7, 0x10, 0x7c, 0x28, 0x27, 0x8c,
48
+ 0x13, 0x95, 0x9c, 0xc7, 0x24, 0x46, 0x3b, 0x70, 0xca, 0xe3, 0x85, 0xcb, 0x11, 0xd0, 0x93, 0xb8,
49
+ 0xa6, 0x83, 0x20, 0xff, 0x9f, 0x77, 0xc3, 0xcc, 0x03, 0x6f, 0x08, 0xbf, 0x40, 0xe7, 0x2b, 0xe2,
50
+ 0x79, 0x0c, 0xaa, 0x82, 0x41, 0x3a, 0xea, 0xb9, 0xe4, 0x9a, 0xa4, 0x97, 0x7e, 0xda, 0x7a, 0x17,
51
+ 0x66, 0x94, 0xa1, 0x1d, 0x3d, 0xf0, 0xde, 0xb3, 0x0b, 0x72, 0xa7, 0x1c, 0xef, 0xd1, 0x53, 0x3e,
52
+ 0x8f, 0x33, 0x26, 0x5f, 0xec, 0x76, 0x2a, 0x49, 0x81, 0x88, 0xee, 0x21, 0xc4, 0x1a, 0xeb, 0xd9,
53
+ 0xc5, 0x39, 0x99, 0xcd, 0xad, 0x31, 0x8b, 0x01, 0x18, 0x23, 0xdd, 0x1f, 0x4e, 0x2d, 0xf9, 0x48,
54
+ 0x4f, 0xf2, 0x65, 0x8e, 0x78, 0x5c, 0x58, 0x19, 0x8d, 0xe5, 0x98, 0x57, 0x67, 0x7f, 0x05, 0x64,
55
+ 0xaf, 0x63, 0xb6, 0xfe, 0xf5, 0xb7, 0x3c, 0xa5, 0xce, 0xe9, 0x68, 0x44, 0xe0, 0x4d, 0x43, 0x69,
56
+ 0x29, 0x2e, 0xac, 0x15, 0x59, 0xa8, 0x0a, 0x9e, 0x6e, 0x47, 0xdf, 0x34, 0x35, 0x6a, 0xcf, 0xdc,
57
+ 0x22, 0xc9, 0xc0, 0x9b, 0x89, 0xd4, 0xed, 0xab, 0x12, 0xa2, 0x0d, 0x52, 0xbb, 0x02, 0x2f, 0xa9,
58
+ 0xd7, 0x61, 0x1e, 0xb4, 0x50, 0x04, 0xf6, 0xc2, 0x16, 0x25, 0x86, 0x56, 0x55, 0x09, 0xbe, 0x91
59
+ ];
60
+
61
+ /** Reed-Solomon matrix for key schedule */
62
+ const RS = [
63
+ [0x01, 0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E],
64
+ [0xA4, 0x56, 0x82, 0xF3, 0x1E, 0xC6, 0x68, 0xE5],
65
+ [0x02, 0xA1, 0xFC, 0xC1, 0x47, 0xAE, 0x3D, 0x19],
66
+ [0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E, 0x03]
67
+ ];
68
+
69
+ /**
70
+ * Galois Field multiplication in GF(2^8) with polynomial 0x169
71
+ */
72
+ function gfMult(a, b, poly) {
73
+ let result = 0;
74
+ while (b) {
75
+ if (b & 1) result ^= a;
76
+ a <<= 1;
77
+ if (a & 0x100) a ^= poly;
78
+ b >>>= 1;
79
+ }
80
+ return result & 0xFF;
81
+ }
82
+
83
+ /**
84
+ * MDS multiplication
85
+ */
86
+ function mdsMultiply(x) {
87
+ const b0 = x & 0xFF;
88
+ const b1 = (x >>> 8) & 0xFF;
89
+ const b2 = (x >>> 16) & 0xFF;
90
+ const b3 = (x >>> 24) & 0xFF;
91
+
92
+ // MDS matrix multiplication in GF(2^8) with polynomial 0x169
93
+ const r0 = gfMult(b0, 0x01, 0x169) ^ gfMult(b1, 0xEF, 0x169) ^ gfMult(b2, 0x5B, 0x169) ^ gfMult(b3, 0x5B, 0x169);
94
+ const r1 = gfMult(b0, 0x5B, 0x169) ^ gfMult(b1, 0xEF, 0x169) ^ gfMult(b2, 0xEF, 0x169) ^ gfMult(b3, 0x01, 0x169);
95
+ const r2 = gfMult(b0, 0xEF, 0x169) ^ gfMult(b1, 0x5B, 0x169) ^ gfMult(b2, 0x01, 0x169) ^ gfMult(b3, 0xEF, 0x169);
96
+ const r3 = gfMult(b0, 0xEF, 0x169) ^ gfMult(b1, 0x01, 0x169) ^ gfMult(b2, 0xEF, 0x169) ^ gfMult(b3, 0x5B, 0x169);
97
+
98
+ return (r3 << 24) | (r2 << 16) | (r1 << 8) | r0;
99
+ }
100
+
101
+ /**
102
+ * Reed-Solomon multiplication for key schedule
103
+ */
104
+ function rsMultiply(key8) {
105
+ let result = 0;
106
+ for (let i = 0; i < 4; i++) {
107
+ let x = 0;
108
+ for (let j = 0; j < 8; j++) {
109
+ x ^= gfMult(RS[i][j], key8[j], 0x14D);
110
+ }
111
+ result |= x << (i * 8);
112
+ }
113
+ return result;
114
+ }
115
+
116
+ /**
117
+ * Apply h function (the main keyed permutation)
118
+ */
119
+ function h(x, L, k) {
120
+ const y = new Array(4);
121
+ y[0] = x & 0xFF;
122
+ y[1] = (x >>> 8) & 0xFF;
123
+ y[2] = (x >>> 16) & 0xFF;
124
+ y[3] = (x >>> 24) & 0xFF;
125
+
126
+ if (k === 4) {
127
+ y[0] = Q1[y[0]] ^ (L[3] & 0xFF);
128
+ y[1] = Q0[y[1]] ^ ((L[3] >>> 8) & 0xFF);
129
+ y[2] = Q0[y[2]] ^ ((L[3] >>> 16) & 0xFF);
130
+ y[3] = Q1[y[3]] ^ ((L[3] >>> 24) & 0xFF);
131
+ }
132
+ if (k >= 3) {
133
+ y[0] = Q1[y[0]] ^ (L[2] & 0xFF);
134
+ y[1] = Q1[y[1]] ^ ((L[2] >>> 8) & 0xFF);
135
+ y[2] = Q0[y[2]] ^ ((L[2] >>> 16) & 0xFF);
136
+ y[3] = Q0[y[3]] ^ ((L[2] >>> 24) & 0xFF);
137
+ }
138
+
139
+ // Always do k >= 2
140
+ y[0] = Q0[Q0[y[0]] ^ (L[1] & 0xFF)] ^ (L[0] & 0xFF);
141
+ y[1] = Q0[Q1[y[1]] ^ ((L[1] >>> 8) & 0xFF)] ^ ((L[0] >>> 8) & 0xFF);
142
+ y[2] = Q1[Q0[y[2]] ^ ((L[1] >>> 16) & 0xFF)] ^ ((L[0] >>> 16) & 0xFF);
143
+ y[3] = Q1[Q1[y[3]] ^ ((L[1] >>> 24) & 0xFF)] ^ ((L[0] >>> 24) & 0xFF);
144
+
145
+ // Final q-box lookup
146
+ y[0] = Q1[y[0]];
147
+ y[1] = Q0[y[1]];
148
+ y[2] = Q1[y[2]];
149
+ y[3] = Q0[y[3]];
150
+
151
+ return mdsMultiply((y[3] << 24) | (y[2] << 16) | (y[1] << 8) | y[0]);
152
+ }
153
+
154
+ /**
155
+ * Rotate left 32-bit
156
+ */
157
+ function ROL(x, n) {
158
+ return ((x << n) | (x >>> (32 - n))) >>> 0;
159
+ }
160
+
161
+ /**
162
+ * Rotate right 32-bit
163
+ */
164
+ function ROR(x, n) {
165
+ return ((x >>> n) | (x << (32 - n))) >>> 0;
166
+ }
167
+
168
+ /**
169
+ * Generate subkeys from the key
170
+ */
171
+ function generateSubkeys(key) {
172
+ const keyLen = key.length;
173
+ const k = keyLen / 8; // 2, 3, or 4
174
+
175
+ // Split key into Me (even words) and Mo (odd words)
176
+ const Me = new Array(k);
177
+ const Mo = new Array(k);
178
+
179
+ for (let i = 0; i < k; i++) {
180
+ const offset = i * 8;
181
+ Me[i] = (key[offset]) | (key[offset + 1] << 8) |
182
+ (key[offset + 2] << 16) | (key[offset + 3] << 24);
183
+ Mo[i] = (key[offset + 4]) | (key[offset + 5] << 8) |
184
+ (key[offset + 6] << 16) | (key[offset + 7] << 24);
185
+ }
186
+
187
+ // Generate S-box keys using Reed-Solomon
188
+ const S = new Array(k);
189
+ for (let i = 0; i < k; i++) {
190
+ const offset = (k - 1 - i) * 8;
191
+ S[i] = rsMultiply(key.slice(offset, offset + 8));
192
+ }
193
+
194
+ // Generate round subkeys
195
+ const subkeys = new Array(40);
196
+ const rho = 0x01010101;
197
+
198
+ for (let i = 0; i < 20; i++) {
199
+ const A = h(2 * i * rho, Me, k);
200
+ const B = ROL(h((2 * i + 1) * rho, Mo, k), 8);
201
+ subkeys[2 * i] = (A + B) >>> 0;
202
+ subkeys[2 * i + 1] = ROL((A + 2 * B) >>> 0, 9);
203
+ }
204
+
205
+ return { subkeys, S, k };
206
+ }
207
+
208
+ /**
209
+ * g function using precomputed S-box keys
210
+ */
211
+ function g(x, S, k) {
212
+ return h(x, S, k);
213
+ }
214
+
215
+ /**
216
+ * Encrypt a single 128-bit block
217
+ */
218
+ function encryptBlock(block, keyData) {
219
+ const { subkeys, S, k } = keyData;
220
+
221
+ // Split block into 4 words (little-endian)
222
+ let R0 = (block[0]) | (block[1] << 8) | (block[2] << 16) | (block[3] << 24);
223
+ let R1 = (block[4]) | (block[5] << 8) | (block[6] << 16) | (block[7] << 24);
224
+ let R2 = (block[8]) | (block[9] << 8) | (block[10] << 16) | (block[11] << 24);
225
+ let R3 = (block[12]) | (block[13] << 8) | (block[14] << 16) | (block[15] << 24);
226
+
227
+ // Input whitening
228
+ R0 ^= subkeys[0];
229
+ R1 ^= subkeys[1];
230
+ R2 ^= subkeys[2];
231
+ R3 ^= subkeys[3];
232
+
233
+ // 16 rounds
234
+ for (let r = 0; r < NROUNDS; r += 2) {
235
+ let T0 = g(R0, S, k);
236
+ let T1 = g(ROL(R1, 8), S, k);
237
+ R2 = ROR(R2 ^ ((T0 + T1 + subkeys[8 + 2 * r]) >>> 0), 1);
238
+ R3 = ROL(R3, 1) ^ ((T0 + 2 * T1 + subkeys[9 + 2 * r]) >>> 0);
239
+
240
+ T0 = g(R2, S, k);
241
+ T1 = g(ROL(R3, 8), S, k);
242
+ R0 = ROR(R0 ^ ((T0 + T1 + subkeys[8 + 2 * r + 2]) >>> 0), 1);
243
+ R1 = ROL(R1, 1) ^ ((T0 + 2 * T1 + subkeys[9 + 2 * r + 2]) >>> 0);
244
+ }
245
+
246
+ // Output whitening (with undo of last swap)
247
+ R2 ^= subkeys[4];
248
+ R3 ^= subkeys[5];
249
+ R0 ^= subkeys[6];
250
+ R1 ^= subkeys[7];
251
+
252
+ // Convert back to bytes (little-endian)
253
+ return [
254
+ R2 & 0xFF, (R2 >>> 8) & 0xFF, (R2 >>> 16) & 0xFF, (R2 >>> 24) & 0xFF,
255
+ R3 & 0xFF, (R3 >>> 8) & 0xFF, (R3 >>> 16) & 0xFF, (R3 >>> 24) & 0xFF,
256
+ R0 & 0xFF, (R0 >>> 8) & 0xFF, (R0 >>> 16) & 0xFF, (R0 >>> 24) & 0xFF,
257
+ R1 & 0xFF, (R1 >>> 8) & 0xFF, (R1 >>> 16) & 0xFF, (R1 >>> 24) & 0xFF
258
+ ];
259
+ }
260
+
261
+ /**
262
+ * Decrypt a single 128-bit block
263
+ */
264
+ function decryptBlock(block, keyData) {
265
+ const { subkeys, S, k } = keyData;
266
+
267
+ // Split block into 4 words (little-endian)
268
+ let R0 = (block[0]) | (block[1] << 8) | (block[2] << 16) | (block[3] << 24);
269
+ let R1 = (block[4]) | (block[5] << 8) | (block[6] << 16) | (block[7] << 24);
270
+ let R2 = (block[8]) | (block[9] << 8) | (block[10] << 16) | (block[11] << 24);
271
+ let R3 = (block[12]) | (block[13] << 8) | (block[14] << 16) | (block[15] << 24);
272
+
273
+ // Input whitening (reverse of output whitening)
274
+ R0 ^= subkeys[4];
275
+ R1 ^= subkeys[5];
276
+ R2 ^= subkeys[6];
277
+ R3 ^= subkeys[7];
278
+
279
+ // 16 rounds in reverse
280
+ for (let r = NROUNDS - 2; r >= 0; r -= 2) {
281
+ let T0 = g(R0, S, k);
282
+ let T1 = g(ROL(R1, 8), S, k);
283
+ R2 = ROL(R2, 1) ^ ((T0 + T1 + subkeys[8 + 2 * r + 2]) >>> 0);
284
+ R3 = ROR(R3 ^ ((T0 + 2 * T1 + subkeys[9 + 2 * r + 2]) >>> 0), 1);
285
+
286
+ T0 = g(R2, S, k);
287
+ T1 = g(ROL(R3, 8), S, k);
288
+ R0 = ROL(R0, 1) ^ ((T0 + T1 + subkeys[8 + 2 * r]) >>> 0);
289
+ R1 = ROR(R1 ^ ((T0 + 2 * T1 + subkeys[9 + 2 * r]) >>> 0), 1);
290
+ }
291
+
292
+ // Output whitening (reverse of input whitening)
293
+ R2 ^= subkeys[0];
294
+ R3 ^= subkeys[1];
295
+ R0 ^= subkeys[2];
296
+ R1 ^= subkeys[3];
297
+
298
+ // Convert back to bytes (little-endian)
299
+ return [
300
+ R2 & 0xFF, (R2 >>> 8) & 0xFF, (R2 >>> 16) & 0xFF, (R2 >>> 24) & 0xFF,
301
+ R3 & 0xFF, (R3 >>> 8) & 0xFF, (R3 >>> 16) & 0xFF, (R3 >>> 24) & 0xFF,
302
+ R0 & 0xFF, (R0 >>> 8) & 0xFF, (R0 >>> 16) & 0xFF, (R0 >>> 24) & 0xFF,
303
+ R1 & 0xFF, (R1 >>> 8) & 0xFF, (R1 >>> 16) & 0xFF, (R1 >>> 24) & 0xFF
304
+ ];
305
+ }
306
+
307
+ /**
308
+ * XOR two 16-byte blocks
309
+ */
310
+ function xorBlocks(a, b) {
311
+ const result = new Array(16);
312
+ for (let i = 0; i < 16; i++) {
313
+ result[i] = a[i] ^ b[i];
314
+ }
315
+ return result;
316
+ }
317
+
318
+ /**
319
+ * Increment counter (little-endian)
320
+ */
321
+ function incrementCounter(counter) {
322
+ const result = [...counter];
323
+ for (let i = 0; i < 16; i++) {
324
+ result[i]++;
325
+ if (result[i] <= 255) break;
326
+ result[i] = 0;
327
+ }
328
+ return result;
329
+ }
330
+
331
+ /**
332
+ * Apply padding to message
333
+ * @param {number[]} message - Original message
334
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
335
+ * @param {number} blockSize - Block size in bytes
336
+ * @returns {number[]} - Padded message
337
+ */
338
+ function applyPadding(message, padding, blockSize) {
339
+ const remainder = message.length % blockSize;
340
+ let nPadding = remainder === 0 ? 0 : blockSize - remainder;
341
+
342
+ // For PKCS5, always add at least one byte (full block if already aligned)
343
+ if (padding === "PKCS5" && remainder === 0) {
344
+ nPadding = blockSize;
345
+ }
346
+
347
+ if (nPadding === 0) return [...message];
348
+
349
+ const paddedMessage = [...message];
350
+
351
+ switch (padding) {
352
+ case "NO":
353
+ throw new OperationError(`No padding requested but input is not a ${blockSize}-byte multiple.`);
354
+
355
+ case "PKCS5":
356
+ for (let i = 0; i < nPadding; i++) {
357
+ paddedMessage.push(nPadding);
358
+ }
359
+ break;
360
+
361
+ case "ZERO":
362
+ for (let i = 0; i < nPadding; i++) {
363
+ paddedMessage.push(0);
364
+ }
365
+ break;
366
+
367
+ case "RANDOM":
368
+ for (let i = 0; i < nPadding; i++) {
369
+ paddedMessage.push(Math.floor(Math.random() * 256));
370
+ }
371
+ break;
372
+
373
+ case "BIT":
374
+ paddedMessage.push(0x80);
375
+ for (let i = 1; i < nPadding; i++) {
376
+ paddedMessage.push(0);
377
+ }
378
+ break;
379
+
380
+ default:
381
+ throw new OperationError(`Unknown padding type: ${padding}`);
382
+ }
383
+
384
+ return paddedMessage;
385
+ }
386
+
387
+ /**
388
+ * Remove padding from message
389
+ * @param {number[]} message - Padded message
390
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
391
+ * @param {number} blockSize - Block size in bytes
392
+ * @returns {number[]} - Unpadded message
393
+ */
394
+ function removePadding(message, padding, blockSize) {
395
+ if (message.length === 0) return message;
396
+
397
+ switch (padding) {
398
+ case "NO":
399
+ case "ZERO":
400
+ case "RANDOM":
401
+ // These padding types cannot be reliably removed
402
+ return message;
403
+
404
+ case "PKCS5": {
405
+ const padByte = message[message.length - 1];
406
+ if (padByte > 0 && padByte <= blockSize) {
407
+ // Verify padding
408
+ for (let i = 0; i < padByte; i++) {
409
+ if (message[message.length - 1 - i] !== padByte) {
410
+ throw new OperationError("Invalid PKCS#5 padding.");
411
+ }
412
+ }
413
+ return message.slice(0, message.length - padByte);
414
+ }
415
+ throw new OperationError("Invalid PKCS#5 padding.");
416
+ }
417
+
418
+ case "BIT": {
419
+ // Find 0x80 byte working backwards, skipping zeros
420
+ for (let i = message.length - 1; i >= 0; i--) {
421
+ if (message[i] === 0x80) {
422
+ return message.slice(0, i);
423
+ } else if (message[i] !== 0) {
424
+ throw new OperationError("Invalid BIT padding.");
425
+ }
426
+ }
427
+ throw new OperationError("Invalid BIT padding.");
428
+ }
429
+
430
+ default:
431
+ throw new OperationError(`Unknown padding type: ${padding}`);
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Encrypt using Twofish cipher with specified block mode
437
+ *
438
+ * @param {number[]} message - Plaintext as byte array
439
+ * @param {number[]} key - Key (16, 24, or 32 bytes)
440
+ * @param {number[]} iv - IV (16 bytes, not used for ECB)
441
+ * @param {string} mode - Block cipher mode ("ECB", "CBC", "CFB", "OFB", "CTR")
442
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
443
+ * @returns {number[]} - Ciphertext as byte array
444
+ */
445
+ export function encryptTwofish(message, key, iv, mode = "ECB", padding = "PKCS5") {
446
+ const messageLength = message.length;
447
+ if (messageLength === 0) return [];
448
+
449
+ const keyData = generateSubkeys(key);
450
+
451
+ // Apply padding for ECB/CBC modes
452
+ let paddedMessage;
453
+ if (mode === "ECB" || mode === "CBC") {
454
+ paddedMessage = applyPadding(message, padding, BLOCKSIZE);
455
+ } else {
456
+ // Stream modes (CFB, OFB, CTR) don't need padding
457
+ paddedMessage = [...message];
458
+ }
459
+
460
+ const cipherText = [];
461
+
462
+ switch (mode) {
463
+ case "ECB":
464
+ for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) {
465
+ const block = paddedMessage.slice(i, i + BLOCKSIZE);
466
+ cipherText.push(...encryptBlock(block, keyData));
467
+ }
468
+ break;
469
+
470
+ case "CBC": {
471
+ let ivBlock = [...iv];
472
+ for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) {
473
+ const block = paddedMessage.slice(i, i + BLOCKSIZE);
474
+ const xored = xorBlocks(block, ivBlock);
475
+ ivBlock = encryptBlock(xored, keyData);
476
+ cipherText.push(...ivBlock);
477
+ }
478
+ break;
479
+ }
480
+
481
+ case "CFB": {
482
+ let ivBlock = [...iv];
483
+ for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) {
484
+ const encrypted = encryptBlock(ivBlock, keyData);
485
+ const block = paddedMessage.slice(i, i + BLOCKSIZE);
486
+ ivBlock = xorBlocks(encrypted, block);
487
+ cipherText.push(...ivBlock);
488
+ }
489
+ return cipherText.slice(0, messageLength);
490
+ }
491
+
492
+ case "OFB": {
493
+ let ivBlock = [...iv];
494
+ for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) {
495
+ ivBlock = encryptBlock(ivBlock, keyData);
496
+ const block = paddedMessage.slice(i, i + BLOCKSIZE);
497
+ cipherText.push(...xorBlocks(ivBlock, block));
498
+ }
499
+ return cipherText.slice(0, messageLength);
500
+ }
501
+
502
+ case "CTR": {
503
+ let counter = [...iv];
504
+ for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) {
505
+ const encrypted = encryptBlock(counter, keyData);
506
+ const block = paddedMessage.slice(i, i + BLOCKSIZE);
507
+ cipherText.push(...xorBlocks(encrypted, block));
508
+ counter = incrementCounter(counter);
509
+ }
510
+ return cipherText.slice(0, messageLength);
511
+ }
512
+
513
+ default:
514
+ throw new OperationError(`Invalid block cipher mode: ${mode}`);
515
+ }
516
+
517
+ return cipherText;
518
+ }
519
+
520
+ /**
521
+ * Decrypt using Twofish cipher with specified block mode
522
+ *
523
+ * @param {number[]} cipherText - Ciphertext as byte array
524
+ * @param {number[]} key - Key (16, 24, or 32 bytes)
525
+ * @param {number[]} iv - IV (16 bytes, not used for ECB)
526
+ * @param {string} mode - Block cipher mode ("ECB", "CBC", "CFB", "OFB", "CTR")
527
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
528
+ * @returns {number[]} - Plaintext as byte array
529
+ */
530
+ export function decryptTwofish(cipherText, key, iv, mode = "ECB", padding = "PKCS5") {
531
+ const originalLength = cipherText.length;
532
+ if (originalLength === 0) return [];
533
+
534
+ const keyData = generateSubkeys(key);
535
+
536
+ if (mode === "ECB" || mode === "CBC") {
537
+ if ((originalLength % BLOCKSIZE) !== 0)
538
+ throw new OperationError(`Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of 16.`);
539
+ } else {
540
+ // Pad for stream modes
541
+ while ((cipherText.length % BLOCKSIZE) !== 0)
542
+ cipherText.push(0);
543
+ }
544
+
545
+ const plainText = [];
546
+
547
+ switch (mode) {
548
+ case "ECB":
549
+ for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
550
+ const block = cipherText.slice(i, i + BLOCKSIZE);
551
+ plainText.push(...decryptBlock(block, keyData));
552
+ }
553
+ break;
554
+
555
+ case "CBC": {
556
+ let ivBlock = [...iv];
557
+ for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
558
+ const block = cipherText.slice(i, i + BLOCKSIZE);
559
+ const decrypted = decryptBlock(block, keyData);
560
+ plainText.push(...xorBlocks(decrypted, ivBlock));
561
+ ivBlock = block;
562
+ }
563
+ break;
564
+ }
565
+
566
+ case "CFB": {
567
+ let ivBlock = [...iv];
568
+ for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
569
+ const encrypted = encryptBlock(ivBlock, keyData);
570
+ const block = cipherText.slice(i, i + BLOCKSIZE);
571
+ plainText.push(...xorBlocks(encrypted, block));
572
+ ivBlock = block;
573
+ }
574
+ return plainText.slice(0, originalLength);
575
+ }
576
+
577
+ case "OFB": {
578
+ let ivBlock = [...iv];
579
+ for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
580
+ ivBlock = encryptBlock(ivBlock, keyData);
581
+ const block = cipherText.slice(i, i + BLOCKSIZE);
582
+ plainText.push(...xorBlocks(ivBlock, block));
583
+ }
584
+ return plainText.slice(0, originalLength);
585
+ }
586
+
587
+ case "CTR": {
588
+ let counter = [...iv];
589
+ for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
590
+ const encrypted = encryptBlock(counter, keyData);
591
+ const block = cipherText.slice(i, i + BLOCKSIZE);
592
+ plainText.push(...xorBlocks(encrypted, block));
593
+ counter = incrementCounter(counter);
594
+ }
595
+ return plainText.slice(0, originalLength);
596
+ }
597
+
598
+ default:
599
+ throw new OperationError(`Invalid block cipher mode: ${mode}`);
600
+ }
601
+
602
+ // Remove padding for ECB/CBC modes
603
+ if (mode === "ECB" || mode === "CBC") {
604
+ return removePadding(plainText, padding, BLOCKSIZE);
605
+ }
606
+
607
+ return plainText.slice(0, originalLength);
608
+ }