|
| 1 | +import { NativeModules } from 'react-native'; |
| 2 | +const Aes = NativeModules.Aes; |
| 3 | +const AesForked = NativeModules.AesForked; |
| 4 | + |
| 5 | +/** |
| 6 | + * Class that exposes two public methods: Encrypt and Decrypt |
| 7 | + * This is used by the KeyringController to encrypt / decrypt the state |
| 8 | + * which contains sensitive seed words and addresses |
| 9 | + */ |
| 10 | +class Encryptor { |
| 11 | + key = null; |
| 12 | + |
| 13 | + _generateSalt(byteCount = 32) { |
| 14 | + const view = new Uint8Array(byteCount); |
| 15 | + global.crypto.getRandomValues(view); |
| 16 | + const b64encoded = btoa(String.fromCharCode.apply(null, Array.from(view))); |
| 17 | + return b64encoded; |
| 18 | + } |
| 19 | + |
| 20 | + _generateKey = ({ |
| 21 | + password, |
| 22 | + salt, |
| 23 | + lib, |
| 24 | + }: { |
| 25 | + password: string; |
| 26 | + salt: string; |
| 27 | + lib: string; |
| 28 | + }) => |
| 29 | + lib === 'original' |
| 30 | + ? Aes.pbkdf2(password, salt, 5000, 256) |
| 31 | + : AesForked.pbkdf2(password, salt); |
| 32 | + |
| 33 | + _keyFromPassword = ({ |
| 34 | + password, |
| 35 | + salt, |
| 36 | + lib, |
| 37 | + }: { |
| 38 | + password: string; |
| 39 | + salt: string; |
| 40 | + lib: string; |
| 41 | + }) => this._generateKey({ password, salt, lib }); |
| 42 | + |
| 43 | + _encryptWithKey = async ({ |
| 44 | + text, |
| 45 | + keyBase64, |
| 46 | + }: { |
| 47 | + text: string; |
| 48 | + keyBase64: string; |
| 49 | + }) => { |
| 50 | + const iv = await Aes.randomKey(16); |
| 51 | + return Aes.encrypt(text, keyBase64, iv).then((cipher: string) => ({ |
| 52 | + cipher, |
| 53 | + iv, |
| 54 | + })); |
| 55 | + }; |
| 56 | + |
| 57 | + _decryptWithKey = ({ |
| 58 | + encryptedData, |
| 59 | + key, |
| 60 | + lib, |
| 61 | + }: { |
| 62 | + encryptedData: { cipher: string; iv: string }; |
| 63 | + key: string; |
| 64 | + lib: string; |
| 65 | + }) => |
| 66 | + lib === 'original' |
| 67 | + ? Aes.decrypt(encryptedData.cipher, key, encryptedData.iv) |
| 68 | + : AesForked.decrypt(encryptedData.cipher, key, encryptedData.iv); |
| 69 | + |
| 70 | + /** |
| 71 | + * Asynchronously encrypts a given object using AES encryption. |
| 72 | + * The encryption process involves generating a salt, deriving a key from the provided password and salt, |
| 73 | + * and then using the key to encrypt the object. The result includes the encrypted data, the salt used, |
| 74 | + * and the library version ('original' in this case). |
| 75 | + * |
| 76 | + * @param params.password - The password used for generating the encryption key. |
| 77 | + * @param params.object - The data object to encrypt. It can be of any type, as it will be stringified during the encryption process. |
| 78 | + * @returns A promise that resolves to a string. The string is a JSON representation of an object containing the encrypted data, the salt used for encryption, and the library version. |
| 79 | + */ |
| 80 | + encrypt = async ({ |
| 81 | + password, |
| 82 | + object, |
| 83 | + }: { |
| 84 | + password: string; |
| 85 | + object: unknown; |
| 86 | + }): Promise<string> => { |
| 87 | + const salt = this._generateSalt(16); |
| 88 | + const key = await this._keyFromPassword({ |
| 89 | + password, |
| 90 | + salt, |
| 91 | + lib: 'original', |
| 92 | + }); |
| 93 | + const result = await this._encryptWithKey({ |
| 94 | + text: JSON.stringify(object), |
| 95 | + keyBase64: key, |
| 96 | + }); |
| 97 | + result.salt = salt; |
| 98 | + result.lib = 'original'; |
| 99 | + return JSON.stringify(result); |
| 100 | + }; |
| 101 | + |
| 102 | + /** |
| 103 | + * Decrypts an encrypted JS object (encryptedString) |
| 104 | + * using a password (and AES decryption with native libraries) |
| 105 | + * |
| 106 | + * @param {string} password - Password used for decryption |
| 107 | + * @param {string} encryptedString - String to decrypt |
| 108 | + * @returns - Promise resolving to decrypted data object |
| 109 | + */ |
| 110 | + decrypt = async ({ |
| 111 | + password, |
| 112 | + encryptedString, |
| 113 | + }: { |
| 114 | + password: string; |
| 115 | + encryptedString: string; |
| 116 | + }) => { |
| 117 | + const encryptedData = JSON.parse(encryptedString); |
| 118 | + const key = await this._keyFromPassword({ |
| 119 | + password, |
| 120 | + salt: encryptedData.salt, |
| 121 | + lib: encryptedData.lib, |
| 122 | + }); |
| 123 | + const data = await this._decryptWithKey({ |
| 124 | + encryptedData, |
| 125 | + key, |
| 126 | + lib: encryptedData.lib, |
| 127 | + }); |
| 128 | + return JSON.parse(data); |
| 129 | + }; |
| 130 | +} |
| 131 | + |
| 132 | +// eslint-disable-next-line import/prefer-default-export |
| 133 | +export { Encryptor }; |
0 commit comments