@1auth/crypto
Advanced tools
Comparing version 0.0.0-alpha.44 to 0.0.0-alpha.45
244
index.js
@@ -21,26 +21,35 @@ import { promisify } from 'node:util' | ||
const defaults = { | ||
// symetricEncryptionKey: randomBytes(32).toString('base64') // 256 bits | ||
symetricEncryptionKey: undefined, | ||
symetricEncryptionMethod: 'chacha20-poly1305', // 2024-05: AES-256 GCM (aes-256-gcm) or ChaCha20-Poly1305 (chacha20-poly1305) | ||
symetricEncryptionEncoding: 'base64', // https://nodejs.org/api/buffer.html#buffers-and-character-encodings | ||
asymetricKey: 'P-384', | ||
// symmetricEncryptionKey: randomBytes(32).toString('base64') // 256 bits | ||
symmetricEncryptionKey: undefined, | ||
symmetricEncryptionMethod: 'chacha20-poly1305', // 2024-05: AES-256 GCM (aes-256-gcm) or ChaCha20-Poly1305 (chacha20-poly1305) | ||
symmetricEncryptionEncoding: 'base64', // https://nodejs.org/api/buffer.html#buffers-and-character-encodings | ||
symmetricSignatureAlgorithm: 'sha3-384', | ||
// symmetricSignatureSecret: randomBytes(32).toString('base64') // 256 bits | ||
symmetricSignatureSecret: undefined, | ||
asymmetricKeyNamedCurve: 'P-384', // P-512 | ||
asymmetricSignatureAlgorithm: 'sha3-384', | ||
digestAlgorithm: 'sha3-384', | ||
hmacAlgorithm: 'sha256', | ||
digestEncoding: 'hex', | ||
signatureEncoding: 'base64' | ||
} | ||
const symetricEncryptionEncodingLengths = {} | ||
const symmetricEncryptionEncodingLengths = {} | ||
const options = {} | ||
export default (opt = {}) => { | ||
Object.assign(options, defaults, opt) | ||
symetricEncryptionEncodingLengths.iv = randomBytes(12).toString( | ||
options.symetricEncryptionEncoding | ||
symmetricEncryptionEncodingLengths.iv = randomBytes(12).toString( | ||
options.symmetricEncryptionEncoding | ||
).length | ||
symetricEncryptionEncodingLengths.ivAndAuthTag = | ||
symetricEncryptionEncodingLengths.iv + | ||
randomBytes(16).toString(options.symetricEncryptionEncoding).length | ||
if (!options.symetricEncryptionKey) { | ||
symmetricEncryptionEncodingLengths.ivAndAuthTag = | ||
symmetricEncryptionEncodingLengths.iv + | ||
randomBytes(16).toString(options.symmetricEncryptionEncoding).length | ||
if (!options.symmetricEncryptionKey) { | ||
console.warn( | ||
"@1auth/crypto symetricEncryptionKey is empty, use a stored secret made from randomBytes(32).toString('base64'). Encryption disabled." | ||
"@1auth/crypto symmetricEncryptionKey is empty, use a stored secret made from randomBytes(32).toString('base64'). Encryption disabled." | ||
) | ||
} | ||
if (!options.symmetricSignatureSecret) { | ||
console.warn( | ||
"@1auth/crypto symmetricSignatureSecret is empty, use a stored secret made from randomBytes(32).toString('base64'). Signature disabled." | ||
) | ||
} | ||
} | ||
@@ -130,6 +139,2 @@ | ||
export const randomSymetricEncryptionKey = () => { | ||
return randomBytes(32).toString('base64') // 256 bits | ||
} | ||
// *** configs *** // | ||
@@ -145,5 +150,6 @@ export const randomId = { | ||
// *** Digests *** // | ||
export const createChecksum = (value, { algorithm } = {}) => { | ||
export const createChecksum = (value, { algorithm, encoding } = {}) => { | ||
algorithm ??= options.digestAlgorithm | ||
return checksum(algorithm).update(value).digest('hex') | ||
encoding ??= options.digestEncoding | ||
return checksum(algorithm).update(value).digest(encoding) | ||
} | ||
@@ -159,15 +165,16 @@ export const createDigest = (value, { algorithm } = {}) => { | ||
export const createEncryptedDigest = (value, { algorithm } = {}) => { | ||
algorithm ??= options.digestAlgorithm | ||
const digest = createDigest(value, { algorithm }) | ||
// encrypting using the symetricEncryptionKey instread of | ||
// encrypting using the symmetricEncryptionKey instead of | ||
// the row encryptionKey to allow lookup, must have fixed iv | ||
return symetricEncrypt(digest, { | ||
encryptionKey: options.symetricEncryptionKey, | ||
return symmetricEncrypt(digest, { | ||
encryptionKey: options.symmetricEncryptionKey, | ||
sub: '', | ||
encoding: options.symetricEncryptionEncoding, | ||
encoding: options.symmetricEncryptionEncoding, | ||
iv: Buffer.from( | ||
options.symetricEncryptionKey.substring( | ||
options.symmetricEncryptionKey.substring( | ||
0, | ||
symetricEncryptionEncodingLengths.iv | ||
symmetricEncryptionEncodingLengths.iv | ||
), | ||
options.symetricEncryptionEncoding | ||
options.symmetricEncryptionEncoding | ||
) | ||
@@ -182,15 +189,15 @@ }) | ||
) => { | ||
const digest = symetricDecrypt(encryptedValue, { | ||
const digest = symmetricDecrypt(encryptedValue, { | ||
encryptionKey, | ||
sub: '', | ||
encoding: options.symetricEncryptionEncoding | ||
encoding: options.symmetricEncryptionEncoding | ||
}) | ||
return symetricEncrypt(digest, { | ||
return symmetricEncrypt(digest, { | ||
encryptionKey: newEncryptionKey, | ||
sub: '', | ||
encoding: options.symetricEncryptionEncoding, | ||
encoding: options.symmetricEncryptionEncoding, | ||
iv: Buffer.from( | ||
newEncryptionKey.substring(0, symetricEncryptionEncodingLengths.iv), | ||
options.symetricEncryptionEncoding | ||
newEncryptionKey.substring(0, symmetricEncryptionEncodingLengths.iv), | ||
options.symmetricEncryptionEncoding | ||
) | ||
@@ -219,15 +226,19 @@ }) | ||
// *** Encryption *** // | ||
// *** Symmetric Encryption *** // | ||
const authTagLength = 16 | ||
export const makeSymetricKey = (sub) => { | ||
if (!options.symetricEncryptionKey) { | ||
export const symmetricRandomEncryptionKey = () => { | ||
return randomBytes(32).toString('base64') // 256 bits | ||
} | ||
export const symmetricGenerateEncryptionKey = (sub) => { | ||
if (!options.symmetricEncryptionKey) { | ||
return { encryptionKey: '', encryptedKey: '' } | ||
} | ||
const encryptionKey = randomSymetricEncryptionKey() | ||
const encryptedKey = symetricEncrypt(encryptionKey, { | ||
encryptionKey: options.symetricEncryptionKey, | ||
const encryptionKey = symmetricRandomEncryptionKey() | ||
const encryptedKey = symmetricEncrypt(encryptionKey, { | ||
encryptionKey: options.symmetricEncryptionKey, | ||
sub, | ||
decoding: 'base64', | ||
encoding: options.symetricEncryptionEncoding | ||
encoding: options.symmetricEncryptionEncoding | ||
}) | ||
@@ -244,3 +255,3 @@ return { encryptionKey, encryptedKey } | ||
if (encryptedKey) { | ||
encryptionKey ??= symetricDecryptKey(encryptedKey, { | ||
encryptionKey ??= symmetricDecryptKey(encryptedKey, { | ||
sub | ||
@@ -252,3 +263,3 @@ }) | ||
for (const key of fields) { | ||
encryptedValues[key] &&= symetricEncrypt(encryptedValues[key], { | ||
encryptedValues[key] &&= symmetricEncrypt(encryptedValues[key], { | ||
encryptionKey, | ||
@@ -261,8 +272,8 @@ sub | ||
export const symetricEncrypt = ( | ||
export const symmetricEncrypt = ( | ||
data, | ||
{ encryptedKey, encryptionKey, sub, decoding, encoding, iv } | ||
{ encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding, iv } | ||
) => { | ||
if (encryptedKey) { | ||
encryptionKey ??= symetricDecryptKey(encryptedKey, { | ||
encryptionKey ??= symmetricDecryptKey(encryptedKey, { | ||
sub | ||
@@ -273,7 +284,7 @@ }) | ||
decoding ??= 'utf8' | ||
encoding ??= options.symetricEncryptionEncoding | ||
encoding ??= options.symmetricEncryptionEncoding | ||
iv ??= randomBytes(12) // 96 bits | ||
const cipher = createCipheriv( | ||
options.symetricEncryptionMethod, | ||
options.symmetricEncryptionMethod, | ||
Buffer.from(encryptionKey, 'base64'), | ||
@@ -293,3 +304,4 @@ iv, | ||
return encryptedDataPacket | ||
// add signature to end | ||
return symmetricSignatureSign(encryptedDataPacket, signatureSecret) | ||
} | ||
@@ -299,7 +311,7 @@ | ||
encryptedValues, | ||
{ encryptedKey, encryptionKey, sub }, | ||
{ encryptedKey, encryptionKey, signatureSecret, sub }, | ||
fields = [] | ||
) => { | ||
if (encryptedKey) { | ||
encryptionKey ??= symetricDecryptKey(encryptedKey, { | ||
encryptionKey ??= symmetricDecryptKey(encryptedKey, { | ||
sub | ||
@@ -311,3 +323,3 @@ }) | ||
for (const key of fields) { | ||
values[key] &&= symetricDecrypt(values[key], { | ||
values[key] &&= symmetricDecrypt(values[key], { | ||
encryptionKey, | ||
@@ -320,7 +332,7 @@ sub | ||
export const symetricDecryptKey = (encryptedKey, { sub } = {}) => { | ||
return symetricDecrypt(encryptedKey, { | ||
encryptionKey: options.symetricEncryptionKey, | ||
export const symmetricDecryptKey = (encryptedKey, { sub } = {}) => { | ||
return symmetricDecrypt(encryptedKey, { | ||
encryptionKey: options.symmetricEncryptionKey, | ||
sub, | ||
decoding: options.symetricEncryptionEncoding, | ||
decoding: options.symmetricEncryptionEncoding, | ||
encoding: 'base64' | ||
@@ -330,8 +342,8 @@ }) | ||
export const symetricDecrypt = ( | ||
export const symmetricDecrypt = ( | ||
encryptedDataPacket, | ||
{ encryptedKey, encryptionKey, sub, decoding, encoding } | ||
{ encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding } | ||
) => { | ||
if (encryptedKey) { | ||
encryptionKey ??= symetricDecryptKey(encryptedKey, { | ||
encryptionKey ??= symmetricDecryptKey(encryptedKey, { | ||
sub | ||
@@ -341,7 +353,17 @@ }) | ||
if (!encryptionKey || !encryptedDataPacket) return encryptedDataPacket | ||
decoding ??= options.symetricEncryptionEncoding | ||
decoding ??= options.symmetricEncryptionEncoding | ||
encoding ??= 'utf8' | ||
// remove signature when successful | ||
encryptedDataPacket = symmetricSignatureVerify( | ||
encryptedDataPacket, | ||
signatureSecret | ||
) | ||
if (encryptedDataPacket === false) { | ||
throw new Error('Signature incorrect') | ||
} | ||
const iv = Buffer.from( | ||
encryptedDataPacket.substring(0, symetricEncryptionEncodingLengths.iv), | ||
encryptedDataPacket.substring(0, symmetricEncryptionEncodingLengths.iv), | ||
decoding | ||
@@ -351,4 +373,4 @@ ) | ||
encryptedDataPacket.substring( | ||
symetricEncryptionEncodingLengths.iv, | ||
symetricEncryptionEncodingLengths.ivAndAuthTag | ||
symmetricEncryptionEncodingLengths.iv, | ||
symmetricEncryptionEncodingLengths.ivAndAuthTag | ||
), | ||
@@ -359,3 +381,3 @@ decoding | ||
encryptedDataPacket.substring( | ||
symetricEncryptionEncodingLengths.ivAndAuthTag | ||
symmetricEncryptionEncodingLengths.ivAndAuthTag | ||
), | ||
@@ -366,3 +388,3 @@ decoding | ||
const decipher = createDecipheriv( | ||
options.symetricEncryptionMethod, | ||
options.symmetricEncryptionMethod, | ||
Buffer.from(encryptionKey, 'base64'), | ||
@@ -383,7 +405,61 @@ iv, | ||
// *** Signatures *** // | ||
// *** Symmetric Signatures *** // | ||
export const symmetricRandomSignatureSecret = () => { | ||
return randomBytes(32).toString('base64') // 256 bits | ||
} | ||
export const symmetricGenerateSignatureSecret = (sub) => { | ||
if (!options.symmetricSignatureSecret) { | ||
return { signatureSecret: '' } | ||
} | ||
const signatureSecret = symmetricRandomSignatureSecret() | ||
return { signatureSecret } | ||
} | ||
export const symmetricSignatureSign = ( | ||
data, | ||
signatureSecret, | ||
{ algorithm } = {} | ||
) => { | ||
// if (typeof data !== 'string') throw new TypeError('data must me a string') | ||
signatureSecret ??= options.symmetricSignatureSecret | ||
algorithm ??= options.symmetricSignatureAlgorithm | ||
const signature = createHmac(algorithm, signatureSecret) | ||
.update(data) | ||
.digest(options.signatureEncoding) | ||
.replace(/=+$/, '') | ||
const signedData = data + '.' + signature | ||
return signedData | ||
} | ||
export const symmetricSignatureVerify = ( | ||
signedData, | ||
signatureSecret, | ||
{ algorithm } = {} | ||
) => { | ||
if (typeof signedData !== 'string') return false | ||
const data = signedData.slice(0, signedData.lastIndexOf('.')) | ||
const signedDataExpected = symmetricSignatureSign(data, signatureSecret, { | ||
algorithm | ||
}) | ||
return safeEqual(signedData, signedDataExpected) && data | ||
} | ||
export const symmetricRotation = ( | ||
encryptedValues, | ||
oldOptions, // { encryptedKey, encryptionKey, signatureSecret, sub }, // old | ||
newOptions, // { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding, iv }, // new | ||
fields = [] | ||
) => { | ||
const data = symmetricDecryptFields(encryptedValues, oldOptions, fields) | ||
const newEncryptedData = symmetricEncryptFields(data, newOptions, fields) | ||
return newEncryptedData | ||
} | ||
// *** Asymmetric Signatures *** // | ||
// asymmetricKeyPairType | ||
export const makeAsymmetricKeys = async () => { | ||
const { publicKey, privateKey } = await generateKeyPair('ec', { | ||
namedCurve: 'P-384', // P-512 | ||
namedCurve: options.asymmetricKeyNamedCurve, | ||
paramEncoding: 'named', | ||
@@ -398,3 +474,3 @@ publicKeyEncoding: { | ||
// Encryption done at another level for consistency | ||
// cipher: options.asymetricEncryptionMethod, | ||
// cipher: options.asymmetricEncryptionMethod, | ||
// passphrase: encryptionKey, | ||
@@ -411,3 +487,3 @@ } | ||
) => { | ||
algorithm ??= options.digestAlgorithm | ||
algorithm ??= options.asymmetricSignatureAlgorithm | ||
return (await sign(algorithm, Buffer.from(data), privateKey)).toString( | ||
@@ -423,3 +499,3 @@ options.signatureEncoding | ||
) => { | ||
algorithm ??= options.digestAlgorithm | ||
algorithm ??= options.asymmetricSignatureAlgorithm | ||
return await verify( | ||
@@ -433,32 +509,2 @@ algorithm, | ||
export const makeSymmetricSignature = ( | ||
data, | ||
encryptionKey, | ||
{ algorithm } = {} | ||
) => { | ||
// if (typeof data !== 'string') throw new TypeError('data must me a string') | ||
encryptionKey ??= options.symetricEncryptionKey | ||
algorithm ??= options.hmacAlgorithm | ||
const signature = createHmac('sha256', encryptionKey) | ||
.update(data) | ||
.digest('base64') | ||
.replace(/=+$/, '') | ||
const signedData = data + '.' + signature | ||
return signedData | ||
} | ||
export const verifySymmetricSignature = ( | ||
signedData, | ||
encryptionKey, | ||
{ algorithm } = {} | ||
) => { | ||
if (typeof signedData !== 'string') return false | ||
const data = signedData.slice(0, signedData.lastIndexOf('.')) | ||
const signedDataExpected = makeSymmetricSignature(data, encryptionKey, { | ||
algorithm | ||
}) | ||
return safeEqual(signedData, signedDataExpected) && data | ||
} | ||
export const safeEqual = (input, expected) => { | ||
@@ -465,0 +511,0 @@ const bufferInput = Buffer.from(input) |
{ | ||
"name": "@1auth/crypto", | ||
"version": "0.0.0-alpha.44", | ||
"version": "0.0.0-alpha.45", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "type": "module", |
15712
447