@@ -165,6 +165,130 @@ test(SUITE, 'GCM wrong key throws error (issue #798)', () => {
165165 expect ( ( ) => decipher . final ( ) ) . to . throw ( ) ;
166166} ) ;
167167
168+ // --- String encoding tests (issue #945) ---
169+
170+ test ( SUITE , 'Buffer concat vs string concat produce same result' , ( ) => {
171+ const testKey = Buffer . from (
172+ 'KTnGEDonslhj/qGvf6rj4HSnO32T7dvjAs5PntTDB0s=' ,
173+ 'base64' ,
174+ ) ;
175+ const testIv = Buffer . from ( '2pXx2krk1wU8RI6AQjuPUg==' , 'base64' ) ;
176+ const text = 'this is a test.' ;
177+
178+ // Buffer concat approach
179+ const cipher1 = createCipheriv ( 'aes-256-cbc' , testKey , testIv ) ;
180+ const bufResult = Buffer . concat ( [
181+ cipher1 . update ( Buffer . from ( text , 'utf8' ) ) ,
182+ cipher1 . final ( ) ,
183+ ] ) . toString ( 'base64' ) ;
184+
185+ // String concat approach (fresh cipher)
186+ const cipher2 = createCipheriv ( 'aes-256-cbc' , testKey , testIv ) ;
187+ const strResult =
188+ cipher2 . update ( text , 'utf8' , 'base64' ) + cipher2 . final ( 'base64' ) ;
189+
190+ expect ( bufResult ) . to . equal ( strResult ) ;
191+ } ) ;
192+
193+ test ( SUITE , 'update with hex input and output encoding' , ( ) => {
194+ const cipher1 = createCipheriv ( 'aes-128-cbc' , key16 , iv ) ;
195+ const bufResult = Buffer . concat ( [
196+ cipher1 . update ( plaintextBuffer ) ,
197+ cipher1 . final ( ) ,
198+ ] ) . toString ( 'hex' ) ;
199+
200+ const cipher2 = createCipheriv ( 'aes-128-cbc' , key16 , iv ) ;
201+ const hexResult =
202+ cipher2 . update ( plaintext , 'utf8' , 'hex' ) + cipher2 . final ( 'hex' ) ;
203+
204+ expect ( bufResult ) . to . equal ( hexResult ) ;
205+ } ) ;
206+
207+ test ( SUITE , 'update with hex input decryption' , ( ) => {
208+ // Encrypt
209+ const cipher = createCipheriv ( 'aes-128-cbc' , key16 , iv ) ;
210+ const encrypted =
211+ cipher . update ( plaintext , 'utf8' , 'hex' ) + cipher . final ( 'hex' ) ;
212+
213+ // Decrypt using hex input encoding
214+ const decipher = createDecipheriv ( 'aes-128-cbc' , key16 , iv ) ;
215+ const decrypted =
216+ decipher . update ( encrypted , 'hex' , 'utf8' ) + decipher . final ( 'utf8' ) ;
217+
218+ expect ( decrypted ) . to . equal ( plaintext ) ;
219+ } ) ;
220+
221+ test ( SUITE , 'update with hex encoding roundtrip (aes-256-cbc)' , ( ) => {
222+ // Encrypt
223+ const cipher = createCipheriv ( 'aes-256-cbc' , key32 , iv ) ;
224+ const encrypted =
225+ cipher . update ( plaintext , 'utf8' , 'hex' ) + cipher . final ( 'hex' ) ;
226+
227+ // Decrypt
228+ const decipher = createDecipheriv ( 'aes-256-cbc' , key32 , iv ) ;
229+ const decrypted =
230+ decipher . update ( encrypted , 'hex' , 'utf8' ) + decipher . final ( 'utf8' ) ;
231+
232+ expect ( decrypted ) . to . equal ( plaintext ) ;
233+ } ) ;
234+
235+ // --- Cipher state violation tests ---
236+
237+ test ( SUITE , 'update after final throws' , ( ) => {
238+ const cipher = createCipheriv ( 'aes-128-cbc' , key16 , iv ) ;
239+ cipher . update ( plaintextBuffer ) ;
240+ cipher . final ( ) ;
241+
242+ expect ( ( ) => cipher . update ( plaintextBuffer ) ) . to . throw ( ) ;
243+ } ) ;
244+
245+ test ( SUITE , 'final called twice throws' , ( ) => {
246+ const cipher = createCipheriv ( 'aes-128-cbc' , key16 , iv ) ;
247+ cipher . update ( plaintextBuffer ) ;
248+ cipher . final ( ) ;
249+
250+ expect ( ( ) => cipher . final ( ) ) . to . throw ( ) ;
251+ } ) ;
252+
253+ test ( SUITE , 'decipher update after final throws' , ( ) => {
254+ // First encrypt something
255+ const cipher = createCipheriv ( 'aes-128-cbc' , key16 , iv ) ;
256+ const encrypted = Buffer . concat ( [
257+ cipher . update ( plaintextBuffer ) ,
258+ cipher . final ( ) ,
259+ ] ) ;
260+
261+ // Decrypt and then try to reuse
262+ const decipher = createDecipheriv ( 'aes-128-cbc' , key16 , iv ) ;
263+ decipher . update ( encrypted ) ;
264+ decipher . final ( ) ;
265+
266+ expect ( ( ) => decipher . update ( encrypted ) ) . to . throw ( ) ;
267+ } ) ;
268+
269+ test ( SUITE , 'cipher works after re-init (createCipheriv)' , ( ) => {
270+ // First use
271+ const cipher1 = createCipheriv ( 'aes-128-cbc' , key16 , iv ) ;
272+ const enc1 = Buffer . concat ( [
273+ cipher1 . update ( plaintextBuffer ) ,
274+ cipher1 . final ( ) ,
275+ ] ) ;
276+
277+ // Second use with same params should produce identical result
278+ const cipher2 = createCipheriv ( 'aes-128-cbc' , key16 , iv ) ;
279+ const enc2 = Buffer . concat ( [
280+ cipher2 . update ( plaintextBuffer ) ,
281+ cipher2 . final ( ) ,
282+ ] ) ;
283+
284+ expect ( enc1 . toString ( 'hex' ) ) . to . equal ( enc2 . toString ( 'hex' ) ) ;
285+
286+ // Verify decryption still works
287+ const decipher = createDecipheriv ( 'aes-128-cbc' , key16 , iv ) ;
288+ const decrypted = Buffer . concat ( [ decipher . update ( enc2 ) , decipher . final ( ) ] ) ;
289+ expect ( decrypted . toString ( 'utf8' ) ) . to . equal ( plaintext ) ;
290+ } ) ;
291+
168292test ( SUITE , 'GCM tampered ciphertext throws error' , ( ) => {
169293 const testKey = Buffer . from ( randomFillSync ( new Uint8Array ( 32 ) ) ) ;
170294 const testIv = randomFillSync ( new Uint8Array ( 12 ) ) ;
0 commit comments