Skip to content

Commit

Permalink
Merge pull request #9 from rambler-digital-solutions/crypto
Browse files Browse the repository at this point in the history
feat(crypto): add crypto utils
  • Loading branch information
andrepolischuk authored Aug 14, 2024
2 parents 8b8eb51 + ef3524c commit ad3e3db
Show file tree
Hide file tree
Showing 18 changed files with 2,268 additions and 6 deletions.
4 changes: 4 additions & 0 deletions .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
"path": "packages/cookie-storage/dist/index.js",
"limit": "990 B"
},
{
"path": "packages/crypto/dist/index.js",
"limit": "27.3 KB"
},
{
"path": "packages/debug/dist/index.js",
"limit": "3.4 KB"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Common utils used by Rambler team

- [@rambler-tech/async](packages/async)
- [@rambler-tech/cookie-storage](packages/cookie-storage)
- [@rambler-tech/crypto](packages/crypto)
- [@rambler-tech/debug](packages/debug)
- [@rambler-tech/dom](packages/dom)
- [@rambler-tech/lhci-report](packages/lhci-report)
Expand Down
15 changes: 15 additions & 0 deletions packages/crypto/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Crypto

Browser crypto utils

## Install

```
npm install -D @rambler-tech/crypto
```

or

```
yarn add -D @rambler-tech/crypto
```
72 changes: 72 additions & 0 deletions packages/crypto/aesrsa.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {getRandomValues, subtle} from './crypto'
import {
bufferFromString,
bufferFromUnicode,
stringFromBuffer,
base64urlFromString
} from './buffers'

function generateAESKey() {
return subtle.generateKey(
{
name: 'AES-GCM',
length: 256
},
true,
['encrypt', 'decrypt']
)
}

async function encryptAES(
key: CryptoKey,
initVector: Uint8Array,
body: string
) {
const encryptedBody = await subtle.encrypt(
{name: 'AES-GCM', iv: initVector},
key,
bufferFromUnicode(body)
)

return base64urlFromString(stringFromBuffer(encryptedBody))
}

function importRSAKey(keyString: string) {
return subtle.importKey(
'spki',
bufferFromString(window.atob(keyString)),
{
name: 'RSA-OAEP',
hash: {name: 'SHA-256'}
},
false,
['wrapKey']
)
}

async function encryptRSA(key: CryptoKey, body: CryptoKey) {
const encryptedBody = await subtle.wrapKey('raw', body, key, {
name: 'RSA-OAEP'
} as RsaOaepParams)

return base64urlFromString(stringFromBuffer(encryptedBody))
}

/** Encrypt with AES and RSA keys */
export async function encryptAESRSA(keyString: string, body: string) {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
const initVector = getRandomValues(new Uint8Array(12))
const initVectorString = base64urlFromString(stringFromBuffer(initVector))

const [aesKey, rsaKey] = await Promise.all([
generateAESKey(),
importRSAKey(keyString)
])

const [encryptedBody, encryptedKey] = await Promise.all([
encryptAES(aesKey, initVector, body),
encryptRSA(rsaKey, aesKey)
])

return `${encryptedBody}.${encryptedKey}.${initVectorString}`
}
32 changes: 32 additions & 0 deletions packages/crypto/bcrypt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import sha from 'sha.js'
import {hash} from './bcryptjs'

/** bcrypt derivation params */
export interface DeriveBcryptKeyParams {
salt: string
token: string
}

/** Derive key with bcrypt */
export async function deriveBcryptKey(
input: string,
{salt, token}: DeriveBcryptKeyParams
) {
const hashString: string = await new Promise((resolve, reject) => {
hash(input, salt, (error, hash) => {
if (error) {
reject(error)
} else {
resolve(hash)
}
})
})

if (!token) {
return hashString
}

return sha('sha512')
.update(hashString + token)
.digest('hex')
}
142 changes: 142 additions & 0 deletions packages/crypto/bcryptjs.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Type definitions for bcryptjs v2.4.0
// Project: https://github.com/dcodeIO/bcrypt.js
// Definitions by: Joshua Filby <https://github.com/Joshua-F/>
// Rafael Kraut <https://github.com/RafaelKr>
// Branislav Holý <https://github.com/branoholy>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.1

/**
* Sets the pseudo random number generator to use as a fallback if neither node's crypto module nor the Web Crypto API is available.
* Please note: It is highly important that the PRNG used is cryptographically secure and that it is seeded properly!
* @param random Function taking the number of bytes to generate as its sole argument, returning the corresponding array of cryptographically secure random byte values.
*/
export declare function setRandomFallback(
random: (random: number) => Buffer
): void

/**
* Synchronously generates a salt.
* @param rounds Number of rounds to use, defaults to 10 if omitted
* @return Resulting salt
* @throws If a random fallback is required but not set
*/
export declare function genSaltSync(rounds?: number): string

/**
* Asynchronously generates a salt.
* @param rounds Number of rounds to use, defaults to 10 if omitted
* @return Promise with resulting salt, if callback has been omitted
*/
export declare function genSalt(rounds?: number): Promise<string>

/**
* Asynchronously generates a salt.
* @param callback Callback receiving the error, if any, and the resulting salt
*/
export declare function genSalt(
callback: (err: Error, salt: string) => void
): void

/**
* Asynchronously generates a salt.
* @param rounds Number of rounds to use, defaults to 10 if omitted
* @param callback Callback receiving the error, if any, and the resulting salt
*/
export declare function genSalt(
rounds: number,
callback: (err: Error, salt: string) => void
): void

/**
* Synchronously generates a hash for the given string.
* @param s String to hash
* @param salt Salt length to generate or salt to use, default to 10
* @return Resulting hash
*/
export declare function hashSync(s: string, salt?: number | string): string

/**
* Asynchronously generates a hash for the given string.
* @param s String to hash
* @param salt Salt length to generate or salt to use
* @return Promise with resulting hash, if callback has been omitted
*/
export declare function hash(s: string, salt: number | string): Promise<string>

/**
* Asynchronously generates a hash for the given string.
* @param s String to hash
* @param salt Salt length to generate or salt to use
* @param callback Callback receiving the error, if any, and the resulting hash
* @param progressCallback Callback successively called with the percentage of rounds completed (0.0 - 1.0), maximally once per MAX_EXECUTION_TIME = 100 ms.
*/
export declare function hash(
s: string,
salt: number | string,
callback?: (err: Error, hash: string) => void,
progressCallback?: (percent: number) => void
): void

/**
* Synchronously tests a string against a hash.
* @param s String to compare
* @param hash Hash to test against
* @return true if matching, otherwise false
*/
export declare function compareSync(s: string, hash: string): boolean

/**
* Asynchronously compares the given data against the given hash.
* @param s Data to compare
* @param hash Data to be compared to
* @return Promise, if callback has been omitted
*/
export declare function compare(s: string, hash: string): Promise<boolean>

/**
* Asynchronously compares the given data against the given hash.
* @param s Data to compare
* @param hash Data to be compared to
* @param callback Callback receiving the error, if any, otherwise the result
* @param progressCallback Callback successively called with the percentage of rounds completed (0.0 - 1.0), maximally once per MAX_EXECUTION_TIME = 100 ms.
*/
export declare function compare(
s: string,
hash: string,
callback?: (err: Error, success: boolean) => void,
progressCallback?: (percent: number) => void
): void

/**
* Gets the number of rounds used to encrypt the specified hash.
* @param hash Hash to extract the used number of rounds from
* @return Number of rounds used
*/
export declare function getRounds(hash: string): number

/**
* Gets the salt portion from a hash. Does not validate the hash.
* @param hash Hash to extract the salt from
* @return Extracted salt part
*/
export declare function getSalt(hash: string): string

/**
* Encodes a byte array to base64 with up to len bytes of input, using the custom bcrypt alphabet.
* @function
* @param b Byte array
* @param len Maximum input length
*/
export declare function encodeBase64(
b: Readonly<ArrayLike<number>>,
len: number
): string

/**
* Decodes a base64 encoded string to up to len bytes of output, using the custom bcrypt alphabet.
* @function
* @param s String to decode
* @param len Maximum output length
*/
export declare function decodeBase64(s: string, len: number): number[]
Loading

0 comments on commit ad3e3db

Please sign in to comment.