-
Notifications
You must be signed in to change notification settings - Fork 476
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
2,078 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { getCachedTextDecoder, getCachedTextEncoder } from "../utils/cache"; | ||
import { | ||
decryptCryptoJSCipherBase64, | ||
parseCryptoJSCipherBase64, | ||
} from "./utils/crypto-js-compat"; | ||
import { base64ToUint8Array } from "../utils/uint8array-extras"; | ||
import { universalCrypto } from "../utils/universal-crypto"; | ||
|
||
/** | ||
* Decrypts ciphertext encrypted with aesEncrypt() using supplied password. | ||
* | ||
* @param ciphertext - Ciphertext to be decrypted. | ||
* @param password - Password to use to decrypt ciphertext. | ||
* @returns Decrypted plaintext. | ||
* | ||
* @example | ||
* const plaintext = await aesDecrypt(ciphertext, 'pw'); | ||
*/ | ||
export async function aesDecrypt( | ||
ciphertext: string, | ||
password: string, | ||
): Promise<string> { | ||
const crypto = await universalCrypto(); | ||
// encode password as UTF-8 | ||
const pwUtf8 = getCachedTextEncoder().encode(password); | ||
// hash the password | ||
const pwHash = await crypto.subtle.digest("SHA-256", pwUtf8); | ||
|
||
const cipherUint8Array = base64ToUint8Array(ciphertext); | ||
|
||
// iv | ||
const iv = cipherUint8Array.slice(0, 12); | ||
|
||
// specify algorithm to use | ||
const alg = { name: "AES-GCM", iv }; | ||
|
||
// generate key from pw | ||
const key = await crypto.subtle.importKey("raw", pwHash, alg, false, [ | ||
"decrypt", | ||
]); | ||
|
||
// ciphertext | ||
const ctUint8 = cipherUint8Array.slice(12); | ||
|
||
try { | ||
// decrypt ciphertext using key | ||
const plainBuffer = await crypto.subtle.decrypt(alg, key, ctUint8); | ||
// return the plaintext from ArrayBuffer | ||
return getCachedTextDecoder().decode(plainBuffer); | ||
} catch (e) { | ||
throw new Error("Decrypt failed"); | ||
} | ||
} | ||
|
||
/** | ||
* Decrypts ciphertext encrypted with aesEncrypt() OR "crypto-js".AES using supplied password. | ||
* | ||
* @param ciphertext - Ciphertext to be decrypted. | ||
* @param password - Password to use to decrypt ciphertext. | ||
* @returns Decrypted plaintext. | ||
* | ||
* @example | ||
* const plaintext = await aesDecryptCompat(ciphertext, 'pw'); | ||
*/ | ||
export async function aesDecryptCompat( | ||
ciphertext: string, | ||
password: string, | ||
): Promise<string> { | ||
// determine if we're dealing with a legacy (cryptojs) ciphertext | ||
const cryptoJs = parseCryptoJSCipherBase64(ciphertext); | ||
if (cryptoJs.salt && cryptoJs.ciphertext) { | ||
return decryptCryptoJSCipherBase64( | ||
cryptoJs.salt, | ||
cryptoJs.ciphertext, | ||
password, | ||
); | ||
} | ||
// otherwise assume it's a ciphertext generated by aesEncrypt() | ||
return aesDecrypt(ciphertext, password); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { | ||
concatUint8Arrays, | ||
uint8ArrayToBase64, | ||
} from "../utils/uint8array-extras"; | ||
import { getCachedTextEncoder } from "../utils/cache"; | ||
import { universalCrypto } from "../utils/universal-crypto"; | ||
|
||
/** | ||
* Encrypts plaintext using AES-GCM with supplied password, for decryption with aesDecrypt(). | ||
* | ||
* @param plaintext - Plaintext to be encrypted. | ||
* @param password - Password to use to encrypt plaintext. | ||
* @returns Encrypted ciphertext. | ||
* | ||
* @example | ||
* const ciphertext = await aesEncrypt('my secret text', 'pw'); | ||
*/ | ||
export async function aesEncrypt( | ||
plaintext: string, | ||
password: string, | ||
): Promise<string> { | ||
const crypto = await universalCrypto(); | ||
const textEncoder = getCachedTextEncoder(); | ||
// encode password as UTF-8 | ||
const pwUtf8 = textEncoder.encode(password); | ||
// hash the password | ||
const pwHash = await crypto.subtle.digest("SHA-256", pwUtf8); | ||
|
||
// get 96-bit random iv | ||
const iv = crypto.getRandomValues(new Uint8Array(12)); | ||
|
||
// specify algorithm to use | ||
const alg = { name: "AES-GCM", iv }; | ||
|
||
// generate key from pw | ||
const key = await crypto.subtle.importKey("raw", pwHash, alg, false, [ | ||
"encrypt", | ||
]); | ||
|
||
// encode plaintext as UTF-8 | ||
const ptUint8 = textEncoder.encode(plaintext); | ||
// encrypt plaintext using key | ||
const ctBuffer = await crypto.subtle.encrypt(alg, key, ptUint8); | ||
|
||
// iv+ciphertext base64-encoded | ||
return uint8ArrayToBase64(concatUint8Arrays([iv, new Uint8Array(ctBuffer)])); | ||
} |
Oops, something went wrong.