@@ -28,14 +28,27 @@ export interface EncryptedData {
28
28
* The encrypted format of the data that needs to be stored.
29
29
*/
30
30
cipherText ?: string ;
31
+
32
+ /**
33
+ * The salt that is used when deriving the encryption key from the password.
34
+ * Only set for new format with nodecg-io >=0.3.
35
+ */
31
36
salt ?: string ;
37
+
38
+ /**
39
+ * The initialization vector used for encryption.
40
+ * Only set for new format with nodecg-io >=0.3.
41
+ */
32
42
iv ?: string ;
33
43
}
34
44
35
45
/**
36
46
* Decrypts the passed encrypted data using the passed encryption key.
37
47
* If the encryption key is wrong, an error will be returned.
38
48
*
49
+ * This function supports the <=0.2 format with the plain password as an
50
+ * encryption key and no iv (read from ciphertext) and the >=0.3 format with the iv and derived key.
51
+ *
39
52
* @param cipherText the ciphertext that needs to be decrypted.
40
53
* @param encryptionKey the encryption key for the encrypted data.
41
54
* @param iv the initialization vector for the encrypted data.
@@ -56,18 +69,28 @@ export function decryptData(
56
69
}
57
70
}
58
71
72
+ /**
73
+ * Encrypts the passed data objedt using the passed encryption key.
74
+ *
75
+ * @param data the data that needs to be encrypted.
76
+ * @param encryptionKey the encryption key that should be used to encrypt the data.
77
+ * @returns a tuple containing the encrypted data and the initialization vector as a hex string.
78
+ */
59
79
export function encryptData ( data : PersistentData , encryptionKey : crypto . lib . WordArray ) : [ string , string ] {
60
80
const iv = crypto . lib . WordArray . random ( 16 ) ;
61
81
const ivText = iv . toString ( ) ;
62
82
const encrypted = crypto . AES . encrypt ( JSON . stringify ( data ) , encryptionKey , { iv } ) ;
63
83
return [ encrypted . toString ( ) , ivText ] ;
64
84
}
65
85
66
- export function deriveEncryptionKey ( password : string , salt : string | undefined ) : string {
67
- if ( salt === undefined ) {
68
- return password ;
69
- }
70
-
86
+ /**
87
+ * Derives a key suitable for encrypting the config from the given password.
88
+ *
89
+ * @param password the password from which the encryption key will be derived.
90
+ * @param salt the salt that is used for key derivation.
91
+ * @returns a hex encoded string of the derived key.
92
+ */
93
+ export function deriveEncryptionKey ( password : string , salt : string ) : string {
71
94
const saltWordArray = crypto . enc . Hex . parse ( salt ) ;
72
95
73
96
return crypto
@@ -78,6 +101,14 @@ export function deriveEncryptionKey(password: string, salt: string | undefined):
78
101
. toString ( crypto . enc . Hex ) ;
79
102
}
80
103
104
+ /**
105
+ * Re-encrypts the passed data to change the password/encryption key.
106
+ * Currently only used to migrate from <=0.2 to >=0.3 config formats but
107
+ * could be used to implement a change password feature in the future.
108
+ * @param data the data that should be re-encrypted.
109
+ * @param oldSecret the previous encryption key or password.
110
+ * @param newSecret the new encryption key.
111
+ */
81
112
export function reEncryptData (
82
113
data : EncryptedData ,
83
114
oldSecret : string | crypto . lib . WordArray ,
@@ -351,14 +382,20 @@ export class PersistenceManager {
351
382
try {
352
383
this . nodecg . log . info ( "Attempting to automatically login..." ) ;
353
384
354
- const salt =
355
- this . encryptedData . value . salt ?? crypto . lib . WordArray . random ( 128 / 8 ) . toString ( crypto . enc . Hex ) ;
385
+ const salt = this . encryptedData . value . salt ?? crypto . lib . WordArray . random ( 128 / 8 ) . toString ( ) ;
386
+ // Check if no salt is present, which is the case for the nodecg-io <=0.2 configs
387
+ // where crypto-js derived the encryption key and managed the salt.
356
388
if ( this . encryptedData . value . salt === undefined ) {
357
- const newSecret = deriveEncryptionKey ( password , salt ) ;
389
+ // Salt is unset when nodecg-io is first started.
358
390
359
391
if ( this . encryptedData . value . cipherText !== undefined ) {
360
- const newSecretWordArray = crypto . enc . Hex . parse ( newSecret ) ;
361
- reEncryptData ( this . encryptedData . value , password , newSecretWordArray ) ;
392
+ // Salt is unset but we have some encrypted data.
393
+ // This means that this is a old config, that we need to migrate to the new format.
394
+
395
+ // Re-encrypt the configuration using our own derived key instead of the password.
396
+ const newEncryptionKey = deriveEncryptionKey ( password , salt ) ;
397
+ const newEncryptionKeyArr = crypto . enc . Hex . parse ( newEncryptionKey ) ;
398
+ reEncryptData ( this . encryptedData . value , password , newEncryptionKeyArr ) ;
362
399
}
363
400
364
401
this . encryptedData . value . salt = salt ;
0 commit comments