11// crypto.js
22
33function serializeArg ( arg ) {
4- if ( arg instanceof Uint8Array || arg instanceof ArrayBuffer ) return arrayBufferToBase64 ( arg ) ;
5- if ( arg && typeof arg === 'object' ) return JSON . stringify ( arg , Object . keys ( arg ) . sort ( ) ) ;
6- return String ( arg ) ;
4+ if ( arg instanceof Uint8Array || arg instanceof ArrayBuffer ) return arrayBufferToBase64 ( arg ) ;
5+ if ( arg && typeof arg === 'object' ) return JSON . stringify ( arg , Object . keys ( arg ) . sort ( ) ) ;
6+ return String ( arg ) ;
77}
88function getSize ( value ) {
9- if ( value instanceof ArrayBuffer ) return value . byteLength ;
10- if ( value instanceof Uint8Array ) return value . byteLength ;
11- if ( typeof value === 'string' ) return value . length * 2 ;
12- if ( typeof value === 'object' && value !== null ) {
13- return new TextEncoder ( ) . encode ( JSON . stringify ( value ) ) . length ;
14- }
15- return 0 ;
9+ if ( value instanceof ArrayBuffer ) return value . byteLength ;
10+ if ( value instanceof Uint8Array ) return value . byteLength ;
11+ if ( typeof value === 'string' ) return value . length * 2 ;
12+ if ( typeof value === 'object' && value !== null ) {
13+ return new TextEncoder ( ) . encode ( JSON . stringify ( value ) ) . length ;
14+ }
15+ return 0 ;
1616}
1717
1818function cacheAsync ( fn ) {
19- const cache = new Map ( ) ;
20- return async ( ...args ) => {
21- const key = args . map ( serializeArg ) . join ( '|' ) ;
22- const start = performance . now ( ) ;
23- if ( cache . has ( key ) ) {
24- const result = await cache . get ( key ) ;
25- let totalUnits = 0 ;
26- for ( const v of cache . values ( ) ) totalUnits += getSize ( await v ) ;
27- console . log ( `[Cached - ${ fn . name } ]: ${ performance . now ( ) - start } ms, cache size: ${ totalUnits } units` ) ;
28- return result ;
29- }
30- const promise = fn ( ...args ) ;
31- cache . set ( key , promise ) ;
32- const result = await promise ;
33- let totalUnits = 0 ;
34- for ( const v of cache . values ( ) ) totalUnits += getSize ( await v ) ;
35- console . log ( `[Executed - ${ fn . name } ]: ${ performance . now ( ) - start } ms, cache size: ${ totalUnits } units` ) ;
36- return result ;
37- } ;
19+ const cache = new Map ( ) ;
20+ return async ( ...args ) => {
21+ const key = args . map ( serializeArg ) . join ( '|' ) ;
22+ const start = performance . now ( ) ;
23+ if ( cache . has ( key ) ) {
24+ const result = await cache . get ( key ) ;
25+ let totalUnits = 0 ;
26+ for ( const v of cache . values ( ) ) totalUnits += getSize ( await v ) ;
27+ console . log ( `[Cached - ${ fn . name } ]: ${ performance . now ( ) - start } ms, cache size: ${ totalUnits } units` ) ;
28+ return result ;
29+ }
30+ const promise = fn ( ...args ) ;
31+ cache . set ( key , promise ) ;
32+ const result = await promise ;
33+ let totalUnits = 0 ;
34+ for ( const v of cache . values ( ) ) totalUnits += getSize ( await v ) ;
35+ console . log ( `[Executed - ${ fn . name } ]: ${ performance . now ( ) - start } ms, cache size: ${ totalUnits } units` ) ;
36+ return result ;
37+ } ;
3838}
3939
4040function buf2hex ( buffer ) {
41- return Array . prototype . map . call ( new Uint8Array ( buffer ) , x => ( '00' + x . toString ( 16 ) ) . slice ( - 2 ) ) . join ( '' ) ;
41+ return Array . prototype . map . call ( new Uint8Array ( buffer ) , x => ( '00' + x . toString ( 16 ) ) . slice ( - 2 ) ) . join ( '' ) ;
4242}
4343
4444async function sha256 ( message ) {
45- const msgBuffer = new TextEncoder ( ) . encode ( message ) ;
46- const hashBuffer = await crypto . subtle . digest ( 'SHA-256' , msgBuffer ) ;
47- return buf2hex ( hashBuffer ) ;
45+ const msgBuffer = new TextEncoder ( ) . encode ( message ) ;
46+ const hashBuffer = await crypto . subtle . digest ( 'SHA-256' , msgBuffer ) ;
47+ return buf2hex ( hashBuffer ) ;
4848}
4949sha256 = cacheAsync ( sha256 )
5050
5151function base64EncodeUnicode ( str ) {
52- return btoa ( encodeURIComponent ( str ) . replace ( / % ( [ 0 - 9 A - F ] { 2 } ) / g, ( m , p1 ) =>
53- String . fromCharCode ( Number . parseInt ( p1 , 16 ) )
54- ) ) ;
52+ return btoa ( encodeURIComponent ( str ) . replace ( / % ( [ 0 - 9 A - F ] { 2 } ) / g, ( m , p1 ) =>
53+ String . fromCharCode ( Number . parseInt ( p1 , 16 ) )
54+ ) ) ;
5555}
5656
5757function base64DecodeUnicode ( str ) {
58- return decodeURIComponent ( atob ( str ) . split ( '' ) . map ( c =>
59- '%' + ( '00' + c . charCodeAt ( 0 ) . toString ( 16 ) ) . slice ( - 2 )
60- ) . join ( '' ) ) ;
58+ return decodeURIComponent ( atob ( str ) . split ( '' ) . map ( c =>
59+ '%' + ( '00' + c . charCodeAt ( 0 ) . toString ( 16 ) ) . slice ( - 2 )
60+ ) . join ( '' ) ) ;
6161}
6262
6363function arrayBufferToBase64 ( buffer ) {
64- let binary = '' ;
65- const bytes = new Uint8Array ( buffer ) ;
66- for ( let i = 0 ; i < bytes . byteLength ; i ++ ) {
67- binary += String . fromCharCode ( bytes [ i ] ) ;
68- }
69- return globalThis . btoa ( binary ) ;
64+ let binary = '' ;
65+ const bytes = new Uint8Array ( buffer ) ;
66+ for ( let i = 0 ; i < bytes . byteLength ; i ++ ) {
67+ binary += String . fromCharCode ( bytes [ i ] ) ;
68+ }
69+ return globalThis . btoa ( binary ) ;
7070}
7171
7272function base64ToArrayBuffer ( base64 ) {
73- const binary = globalThis . atob ( base64 ) ;
74- const bytes = new Uint8Array ( binary . length ) ;
75- for ( let i = 0 ; i < binary . length ; i ++ ) {
76- bytes [ i ] = binary . charCodeAt ( i ) ;
77- }
78- return bytes . buffer ;
73+ const binary = globalThis . atob ( base64 ) ;
74+ const bytes = new Uint8Array ( binary . length ) ;
75+ for ( let i = 0 ; i < binary . length ; i ++ ) {
76+ bytes [ i ] = binary . charCodeAt ( i ) ;
77+ }
78+ return bytes . buffer ;
7979}
8080
8181function base64ToUint8Array ( base64 ) {
82- const binaryString = atob ( base64 ) ;
83- const len = binaryString . length ;
84- const bytes = new Uint8Array ( len ) ;
85- for ( let i = 0 ; i < len ; i ++ ) {
86- bytes [ i ] = binaryString . charCodeAt ( i ) ;
87- }
88- return bytes ;
82+ const binaryString = atob ( base64 ) ;
83+ const len = binaryString . length ;
84+ const bytes = new Uint8Array ( len ) ;
85+ for ( let i = 0 ; i < len ; i ++ ) {
86+ bytes [ i ] = binaryString . charCodeAt ( i ) ;
87+ }
88+ return bytes ;
8989}
9090
9191async function deriveKey ( password , salt ) {
92- const encoder = new TextEncoder ( ) ;
93- const keyMaterial = await crypto . subtle . importKey ( 'raw' , encoder . encode ( password ) , 'PBKDF2' , false , [ 'deriveKey' ] ) ;
94- return crypto . subtle . deriveKey (
95- { name : 'PBKDF2' , salt : salt , iterations : 100000 , hash : 'SHA-256' } ,
96- keyMaterial ,
97- { name : 'AES-GCM' , length : 256 } ,
98- false ,
99- [ 'encrypt' , 'decrypt' ]
100- ) ;
92+ const encoder = new TextEncoder ( ) ;
93+ const keyMaterial = await crypto . subtle . importKey ( 'raw' , encoder . encode ( password ) , 'PBKDF2' , false , [ 'deriveKey' ] ) ;
94+ return crypto . subtle . deriveKey (
95+ { name : 'PBKDF2' , salt : salt , iterations : 100000 , hash : 'SHA-256' } ,
96+ keyMaterial ,
97+ { name : 'AES-GCM' , length : 256 } ,
98+ false ,
99+ [ 'encrypt' , 'decrypt' ]
100+ ) ;
101101}
102102deriveKey = cacheAsync ( deriveKey ) ;
103103
104104async function encryptText ( plainText , password ) {
105- const encoder = new TextEncoder ( ) ;
106- const salt = crypto . getRandomValues ( new Uint8Array ( 16 ) ) ;
107- const iv = crypto . getRandomValues ( new Uint8Array ( 12 ) ) ;
108- const key = await deriveKey ( password , salt ) ;
109- const encrypted = await crypto . subtle . encrypt (
110- { name : 'AES-GCM' , iv : iv } ,
111- key ,
112- encoder . encode ( plainText )
113- ) ;
114- return JSON . stringify ( {
115- salt : arrayBufferToBase64 ( salt ) ,
116- iv : arrayBufferToBase64 ( iv ) ,
117- data : arrayBufferToBase64 ( encrypted )
118- } ) ;
105+ const encoder = new TextEncoder ( ) ;
106+ const salt = crypto . getRandomValues ( new Uint8Array ( 16 ) ) ;
107+ const iv = crypto . getRandomValues ( new Uint8Array ( 12 ) ) ;
108+ const key = await deriveKey ( password , salt ) ;
109+ const encrypted = await crypto . subtle . encrypt (
110+ { name : 'AES-GCM' , iv : iv } ,
111+ key ,
112+ encoder . encode ( plainText )
113+ ) ;
114+ return JSON . stringify ( {
115+ salt : arrayBufferToBase64 ( salt ) ,
116+ iv : arrayBufferToBase64 ( iv ) ,
117+ data : arrayBufferToBase64 ( encrypted )
118+ } ) ;
119119}
120120
121121async function decryptText ( encryptedData , password ) {
122- try {
123- const obj = JSON . parse ( encryptedData ) ;
124- const salt = new Uint8Array ( base64ToArrayBuffer ( obj . salt ) ) ;
125- const iv = new Uint8Array ( base64ToArrayBuffer ( obj . iv ) ) ;
126- const data = base64ToArrayBuffer ( obj . data ) ;
127- const key = await deriveKey ( password , salt ) ;
128- const decrypted = await crypto . subtle . decrypt (
129- { name : 'AES-GCM' , iv : iv } ,
130- key ,
131- data
132- ) ;
133- return new TextDecoder ( ) . decode ( decrypted ) ;
134- } catch ( e ) {
135- console . error ( e ) ;
136- throw new Error ( "Invalid password or corrupted data" ) ;
137- }
122+ try {
123+ const obj = JSON . parse ( encryptedData ) ;
124+ const salt = new Uint8Array ( base64ToArrayBuffer ( obj . salt ) ) ;
125+ const iv = new Uint8Array ( base64ToArrayBuffer ( obj . iv ) ) ;
126+ const data = base64ToArrayBuffer ( obj . data ) ;
127+ const key = await deriveKey ( password , salt ) ;
128+ const decrypted = await crypto . subtle . decrypt (
129+ { name : 'AES-GCM' , iv : iv } ,
130+ key ,
131+ data
132+ ) ;
133+ return new TextDecoder ( ) . decode ( decrypted ) ;
134+ } catch ( e ) {
135+ console . error ( e ) ;
136+ throw new Error ( "Invalid password or corrupted data" ) ;
137+ }
138138}
139139decryptText = cacheAsync ( decryptText ) ;
140140
141- async function generateOTP ( keyObj ) {
142- const digits = keyObj . digits ;
143- const period = keyObj . period ;
144- const algorithm = ( ( ) => {
145- switch ( keyObj . algorithm ) {
146- case "SHA1" : return "SHA-1" ;
147- case "SHA256" : return "SHA-256" ;
148- case "SHA512" : return "SHA-512" ;
149- default :
150- console . error ( `generateOTP: Unsupported algorithm "${ keyObj . algorithm } "` ) ;
151- throw new Error ( "Unsupported algorithm" ) ;
152- }
153- } ) ( ) ;
154-
155- const epoch = Math . floor ( Date . now ( ) / 1000 ) ;
156- const counter = BigInt ( Math . floor ( epoch / period ) ) ;
157- const buffer = new ArrayBuffer ( 8 ) ;
158- new DataView ( buffer ) . setBigUint64 ( 0 , counter , false ) ;
159-
160- const keyData = base32ToUint8Array ( keyObj . secret ) ;
161-
162- const cryptoKey = await crypto . subtle . importKey (
163- 'raw' ,
164- keyData ,
165- { name : 'HMAC' , hash : { name : algorithm } } ,
166- false ,
167- [ 'sign' ]
168- ) ;
169-
170- const hmac = await crypto . subtle . sign ( 'HMAC' , cryptoKey , buffer ) ;
171- const hmacBytes = new Uint8Array ( hmac ) ;
172- const offset = hmacBytes [ hmacBytes . length - 1 ] & 0xf ;
173-
174- const binary =
175- ( BigInt ( hmacBytes [ offset ] & 0x7f ) << 24n ) |
176- ( BigInt ( hmacBytes [ offset + 1 ] & 0xff ) << 16n ) |
177- ( BigInt ( hmacBytes [ offset + 2 ] & 0xff ) << 8n ) |
178- BigInt ( hmacBytes [ offset + 3 ] & 0xff ) ;
179-
180- const otp = ( binary % 10n ** BigInt ( digits ) ) . toString ( ) . padStart ( digits , '0' ) ;
181-
182- return otp ;
141+ async function generateOTP ( keyObj , offset = 0 ) {
142+ const digits = keyObj . digits ;
143+ const period = keyObj . period ;
144+ const algorithm = ( ( ) => {
145+ switch ( keyObj . algorithm ) {
146+ case "SHA1" : return "SHA-1" ;
147+ case "SHA256" : return "SHA-256" ;
148+ case "SHA512" : return "SHA-512" ;
149+ default :
150+ console . error ( `generateOTP: Unsupported algorithm "${ keyObj . algorithm } "` ) ;
151+ throw new Error ( "Unsupported algorithm" ) ;
152+ }
153+ } ) ( ) ;
154+
155+ const epoch = Math . floor ( Date . now ( ) / 1000 ) + offset * period ;
156+ const counter = BigInt ( Math . floor ( epoch / period ) ) ;
157+ const buffer = new ArrayBuffer ( 8 ) ;
158+ new DataView ( buffer ) . setBigUint64 ( 0 , counter , false ) ;
159+
160+ const keyData = base32ToUint8Array ( keyObj . secret ) ;
161+
162+ const cryptoKey = await crypto . subtle . importKey (
163+ 'raw' ,
164+ keyData ,
165+ { name : 'HMAC' , hash : { name : algorithm } } ,
166+ false ,
167+ [ 'sign' ]
168+ ) ;
169+
170+ const hmac = await crypto . subtle . sign ( 'HMAC' , cryptoKey , buffer ) ;
171+ const hmacBytes = new Uint8Array ( hmac ) ;
172+ const offsetByte = hmacBytes [ hmacBytes . length - 1 ] & 0xf ;
173+
174+ const binary =
175+ ( BigInt ( hmacBytes [ offsetByte ] & 0x7f ) << 24n ) |
176+ ( BigInt ( hmacBytes [ offsetByte + 1 ] & 0xff ) << 16n ) |
177+ ( BigInt ( hmacBytes [ offsetByte + 2 ] & 0xff ) << 8n ) |
178+ BigInt ( hmacBytes [ offsetByte + 3 ] & 0xff ) ;
179+
180+ const otp = ( binary % 10n ** BigInt ( digits ) ) . toString ( ) . padStart ( digits , '0' ) ;
181+
182+ return otp ;
183183}
184184
185185function base32ToUint8Array ( base32 ) {
186- const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' ;
187- let bits = '' , output = [ ] ;
188-
189- for ( let char of base32 ) {
190- let val = alphabet . indexOf ( char . toUpperCase ( ) ) ;
191- if ( val === - 1 ) continue ;
192- bits += val . toString ( 2 ) . padStart ( 5 , '0' ) ;
193- }
194-
195- for ( let i = 0 ; i + 8 <= bits . length ; i += 8 ) {
196- output . push ( Number . parseInt ( bits . substring ( i , i + 8 ) , 2 ) ) ;
197- }
198-
199- return new Uint8Array ( output ) ;
186+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' ;
187+ let bits = '' , output = [ ] ;
188+
189+ for ( let char of base32 ) {
190+ let val = alphabet . indexOf ( char . toUpperCase ( ) ) ;
191+ if ( val === - 1 ) continue ;
192+ bits += val . toString ( 2 ) . padStart ( 5 , '0' ) ;
193+ }
194+
195+ for ( let i = 0 ; i + 8 <= bits . length ; i += 8 ) {
196+ output . push ( Number . parseInt ( bits . substring ( i , i + 8 ) , 2 ) ) ;
197+ }
198+
199+ return new Uint8Array ( output ) ;
200200}
201201
202202function base64ToBase32 ( base64 ) {
203- const bytes = base64ToUint8Array ( base64 ) ;
204- const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' ;
205- let bits = '' , base32 = '' ;
203+ const bytes = base64ToUint8Array ( base64 ) ;
204+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' ;
205+ let bits = '' , base32 = '' ;
206206
207- for ( let byte of bytes ) {
208- bits += byte . toString ( 2 ) . padStart ( 8 , '0' ) ;
209- }
207+ for ( let byte of bytes ) {
208+ bits += byte . toString ( 2 ) . padStart ( 8 , '0' ) ;
209+ }
210210
211- for ( let i = 0 ; i < bits . length ; i += 5 ) {
212- const chunk = bits . substring ( i , i + 5 ) ;
213- base32 += alphabet [ Number . parseInt ( chunk . padEnd ( 5 , '0' ) , 2 ) ] ;
214- }
211+ for ( let i = 0 ; i < bits . length ; i += 5 ) {
212+ const chunk = bits . substring ( i , i + 5 ) ;
213+ base32 += alphabet [ Number . parseInt ( chunk . padEnd ( 5 , '0' ) , 2 ) ] ;
214+ }
215215
216- return base32 ;
216+ return base32 ;
217217}
0 commit comments