@@ -14,11 +14,16 @@ See the License for the specific language governing permissions and
1414limitations under the License.
1515*/
1616
17- import * as crypto from "crypto" ;
17+ import { createCipheriv , createDecipheriv , privateDecrypt , publicEncrypt , scrypt as scryptCb } from "node: crypto" ;
1818import * as fs from "fs" ;
1919import { getLogger } from "../logging" ;
20+ import { randomBytes } from "node:crypto" ;
21+ import { promisify } from "node:util" ;
22+
23+ const scrypt = promisify ( scryptCb ) ;
2024
2125const log = getLogger ( "CryptoStore" ) ;
26+ const algorithm = 'aes-256-cbc' ;
2227
2328export class StringCrypto {
2429 private privateKey ! : string ;
@@ -29,7 +34,7 @@ export class StringCrypto {
2934
3035 // Test whether key is a valid PEM key (publicEncrypt does internal validation)
3136 try {
32- crypto . publicEncrypt (
37+ publicEncrypt (
3338 this . privateKey ,
3439 Buffer . from ( "This is a test!" )
3540 ) ;
@@ -48,41 +53,59 @@ export class StringCrypto {
4853 }
4954
5055 public encrypt ( plaintext : string ) : string {
51- if ( plaintext . includes ( ' ' ) ) {
52- throw Error ( 'Cannot encode spaces' )
53- }
54- const salt = crypto . randomBytes ( 16 ) . toString ( 'base64' ) ;
55- return crypto . publicEncrypt (
56+ const salt = randomBytes ( 16 ) . toString ( 'base64' ) ;
57+ return publicEncrypt (
5658 this . privateKey ,
5759 Buffer . from ( salt + ' ' + plaintext )
5860 ) . toString ( 'base64' ) ;
5961 }
6062
61- public encryptLargeString ( plaintext : string ) : string {
62- const cryptoParts = [ ] ;
63- while ( plaintext . length > 0 ) {
64- const part = plaintext . slice ( 0 , 64 ) ;
65- cryptoParts . push ( this . encrypt ( part ) ) ;
66- plaintext = plaintext . slice ( 64 ) ;
67- }
68- return 'lg:' + cryptoParts . join ( ',' ) ;
69- }
70-
71-
7263 public decrypt ( encryptedString : string ) : string {
73- const decryptedPass = crypto . privateDecrypt (
64+ const decryptedPass = privateDecrypt (
7465 this . privateKey ,
7566 Buffer . from ( encryptedString , 'base64' )
7667 ) . toString ( ) ;
7768 // Extract the password by removing the prefixed salt and seperating space
78- return decryptedPass . slice ( 17 ) [ 1 ] ;
69+ return decryptedPass . slice ( 25 ) ;
70+ }
71+
72+ public async encryptLargeString ( plaintext : string ) : Promise < string > {
73+ const password = randomBytes ( 32 ) . toString ( 'base64' ) ;
74+ const key = await scrypt ( password , 'salt' , 32 ) as Buffer ;
75+ const iv = randomBytes ( 16 ) ;
76+ const cipher = createCipheriv ( algorithm , key , iv ) ;
77+ cipher . setEncoding ( 'base64' ) ;
78+ let encrypted = '' ;
79+ const secret = this . encrypt ( `${ key . toString ( 'base64' ) } _${ iv . toString ( 'base64' ) } ` ) ;
80+ const streamPromise = new Promise < string > ( ( resolve , reject ) => {
81+ cipher . on ( 'error' , ( err ) => reject ( err ) ) ;
82+ cipher . on ( 'end' , ( ) => resolve (
83+ `lg:${ secret } :${ encrypted } `
84+ ) ) ;
85+ } ) ;
86+ cipher . on ( 'data' , ( chunk ) => { encrypted += chunk } ) ;
87+ cipher . write ( plaintext ) ;
88+ cipher . end ( ) ;
89+ return streamPromise ;
7990 }
8091
81- public decryptLargeString ( encryptedString : string ) : string {
82- if ( encryptedString !== 'lg:' ) {
92+ public async decryptLargeString ( encryptedString : string ) : Promise < string > {
93+ if ( ! encryptedString . startsWith ( 'lg:' ) ) {
8394 throw Error ( 'Not a large string' ) ;
8495 }
85- encryptedString = encryptedString . slice ( 3 ) ;
86- return encryptedString . split ( ',' ) . map ( v => this . decrypt ( v ) ) . join ( '' ) ;
96+ const [ , keyPlusIvEnc , data ] = encryptedString . split ( ':' , 3 ) ;
97+ const [ keyB64 , ivB64 ] = this . decrypt ( keyPlusIvEnc ) . split ( '_' ) ;
98+ const iv = Buffer . from ( ivB64 , "base64" ) ;
99+ const key = Buffer . from ( keyB64 , "base64" ) ;
100+ const decipher = createDecipheriv ( algorithm , key , iv ) ;
101+ let decrypted = '' ;
102+ decipher . on ( 'data' , ( chunk ) => { decrypted += chunk } ) ;
103+ const streamPromise = new Promise < string > ( ( resolve , reject ) => {
104+ decipher . on ( 'error' , ( err ) => reject ( err ) ) ;
105+ decipher . on ( 'end' , ( ) => resolve ( decrypted ) ) ;
106+ } ) ;
107+ decipher . write ( Buffer . from ( data , 'base64' ) ) ;
108+ decipher . end ( ) ;
109+ return streamPromise ;
87110 }
88111}
0 commit comments