Skip to content

Commit

Permalink
feat: add key generation
Browse files Browse the repository at this point in the history
  • Loading branch information
frytg committed Dec 17, 2024
1 parent 3aea9d8 commit 1e71ba6
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 16 deletions.
1 change: 1 addition & 0 deletions crypto/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 2024-12-17 - 0.1.0

- feat: add `bufferFromBase64`
- feat: add key generation

## 2024-12-16 - 0.0.3

Expand Down
17 changes: 17 additions & 0 deletions crypto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ This package provides functions to compute SHA-256 and SHA-512 hashes using [nat
- [full docs on JSR](https://jsr.io/@frytg/crypto/doc)
- [Hash (SHA-256 or SHA-512)](https://jsr.io/@frytg/crypto/doc/hash)
- [HMAC (SHA-256 or SHA-512)](https://jsr.io/@frytg/crypto/doc/hmac)
- [generate default keys](https://jsr.io/@frytg/crypto/doc/generate-default-keys)
- [generate key](https://jsr.io/@frytg/crypto/doc/generate-key)

## Generate default keys

You can generate default keys for common key sizes by running the following command:

```bash
deno run jsr:@frytg/crypto/generate-default-keys
```

The output will be printed to the console.

Those can be used for HMAC or encryption key generation.
Google Cloud Storage for example uses 32 byte keys (in base64) for [customer-supplied](https://cloud.google.com/storage/docs/encryption/customer-supplied-keys) [encryption keys](https://cloud.google.com/storage/docs/encryption/using-customer-supplied-keys#storage-upload-encrypted-object-nodejs).

For SHA-256 the recommended key size is 32 bytes (256 bits) and for SHA-512 it is 64 bytes (512 bits) (see [RFC 2104](https://www.rfc-editor.org/rfc/rfc2104#section-2) - section 2 and 3).

## Author

Expand Down
4 changes: 3 additions & 1 deletion crypto/deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"version": "0.1.0",
"exports": {
"./hash": "./hash.ts",
"./hmac": "./hmac.ts"
"./hmac": "./hmac.ts",
"./generate-default-keys": "./generate-default-keys.ts",
"./generate-key": "./generate-key.ts"
},
"publish": {
"exclude": ["*.test.ts"]
Expand Down
16 changes: 16 additions & 0 deletions crypto/generate-default-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* @module
* Run key generation for default key sizes and print the results to the console
*
* @example
* ```bash
* deno run jsr:@frytg/crypto/generate-default-keys
* ```
*/

// load module
import { generateKey } from './generate-key.ts'

generateKey(32)
generateKey(64)
generateKey(128)
71 changes: 71 additions & 0 deletions crypto/generate-key.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// load packages
import { Buffer } from 'node:buffer'
import { test } from '@cross/test'
import { assertEquals, assertExists } from '@std/assert'

// load module
import { generateKey } from './generate-key.ts'

test('generateKey - generates keys of correct length', () => {
const testCases = [{ bytes: 16 }, { bytes: 32 }, { bytes: 64 }, { bytes: 128 }]

for (const { bytes } of testCases) {
const key = generateKey(bytes, true)

// Check buffer length
assertEquals(key.buffer.length, bytes, `Buffer should be ${bytes} bytes long`)

// Check hex length (2 characters per byte)
assertEquals(key.hex.length, bytes * 2, `Hex string should be ${bytes * 2} characters long`)
}
})

test('generateKey - generates different keys each time', () => {
const keys = new Set()
const numKeys = 100
const bytes = 32

// Generate multiple keys
for (let i = 0; i < numKeys; i++) {
const key = generateKey(bytes, true)
keys.add(key.hex)
}

// All keys should be unique
assertEquals(keys.size, numKeys, `All ${numKeys} generated keys should be unique`)
})

test('generateKey - returns consistent encodings', () => {
const key = generateKey(32, true)

// Buffer to base64
assertEquals(key.base64, key.buffer.toString('base64'), 'base64 encoding should match Buffer.toString("base64")')

// Buffer to hex
assertEquals(key.hex, key.buffer.toString('hex'), 'hex encoding should match Buffer.toString("hex")')

// base64 back to buffer
assertEquals(Buffer.from(key.base64, 'base64'), key.buffer, 'base64 string should convert back to original buffer')

// hex back to buffer
assertEquals(Buffer.from(key.hex, 'hex'), key.buffer, 'hex string should convert back to original buffer')
})

test('generateKey - uses default length of 32 bytes', () => {
const key = generateKey(undefined, true)
assertEquals(key.buffer.length, 32, 'Default key length should be 32 bytes')
})

test('generateKey - returns object with required properties', () => {
const key = generateKey(32, true)

// Check that all properties exist
assertExists(key.buffer, 'Should have buffer property')
assertExists(key.base64, 'Should have base64 property')
assertExists(key.hex, 'Should have hex property')

// Check property types
assertEquals(key.buffer instanceof Buffer, true, 'buffer should be Buffer instance')
assertEquals(typeof key.base64, 'string', 'base64 should be string')
assertEquals(typeof key.hex, 'string', 'hex should be string')
})
57 changes: 57 additions & 0 deletions crypto/generate-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// deno-lint-ignore-file no-console
/**
* @module
* {@linkcode generateKey | Generate a key}
*
* @example
* ```ts
* import { generateKey } from '@frytg/crypto/generate-key'
*
* generateKey(32, true)
* ```
*/

// demo provided by
// https://cloud.google.com/storage/docs/encryption/using-customer-supplied-keys#storage-generate-encryption-key-nodejs

// load packages
import type { Buffer } from 'node:buffer'
import crypto from 'node:crypto'

/**
* Generates a key of the specified number of bytes and prints the key in base64 and hex to the console
*
* @param {number} bytes - The number of bytes to generate
* @param {boolean} skipConsole - Whether to skip printing to the console
* @returns {Object} The key in base64 and hex
*
* @example
* ```ts
* import { generateKey } from '@frytg/crypto/generate-key'
*
* generateKey(32, true)
* ```
*/
export const generateKey = (bytes = 32, skipConsole = false): { buffer: Buffer; base64: string; hex: string } => {
// generate key
if (!skipConsole) console.group(`Generating ${bytes} byte key...\n`)

// generate key
const buffer = crypto.randomBytes(bytes)

// encode key in base64
const encodedKeyBase64 = buffer.toString('base64')
if (!skipConsole) console.log(`Base 64 encoded key:\n${encodedKeyBase64}\n`)

// encode key in hex
const encodedKeyHex = buffer.toString('hex')
if (!skipConsole) console.log(`Hex encoded key:\n${encodedKeyHex}\n`)

if (!skipConsole) console.groupEnd()

return {
buffer,
base64: encodedKeyBase64,
hex: encodedKeyHex,
}
}
8 changes: 4 additions & 4 deletions crypto/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ const HEX_ENCODING = 'hex'

/**
* Returns a SHA-256 hash of the input string (as hexadecimal string)
* @param str - The string to hash
* @returns SHA-256 hash of the input string (hexadecimal)
* @param {string} str - The string to hash
* @returns {string} SHA-256 hash of the input string (hexadecimal)
*
* @example SHA-256
* ```ts
Expand All @@ -31,8 +31,8 @@ export const hashSha256 = (str: string): string => createHash('sha256').update(s

/**
* Returns a SHA-512 hash of the input string (as hexadecimal string)
* @param str - The string to hash
* @returns SHA-512 hash of the input string (hexadecimal)
* @param {string} str - The string to hash
* @returns {string} SHA-512 hash of the input string (hexadecimal)
*
* @example SHA-512
* ```ts
Expand Down
20 changes: 10 additions & 10 deletions crypto/hmac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ const HEX_REGEX = /^[0-9a-fA-F]*$/

/**
* Returns a HMAC SHA-256 hash of the input string (as hexadecimal string)
* @param str - The string to hash
* @param key - The key to use for the HMAC, when a string is provided, it will be converted to a Buffer
* @returns HMAC SHA-256 hash of the input string (hexadecimal)
* @param {string | Buffer} str - The string to hash
* @param {string | Buffer} key - The key to use for the HMAC, when a string is provided, it will be converted to a Buffer
* @returns {string} HMAC SHA-256 hash of the input string (hexadecimal)
*
* @example
* ```ts
Expand All @@ -39,9 +39,9 @@ export const hmacSha256 = (str: string | Buffer, key: string | Buffer): string =

/**
* Returns a HMAC SHA-512 hash of the input string (as hexadecimal string)
* @param str - The string to hash
* @param key - The key to use for the HMAC, when a string is provided, it will be converted to a Buffer
* @returns HMAC SHA-512 hash of the input string (hexadecimal)
* @param {string | Buffer} str - The string to hash
* @param {string | Buffer} key - The key to use for the HMAC, when a string is provided, it will be converted to a Buffer
* @returns {string} HMAC SHA-512 hash of the input string (hexadecimal)
*
* @example
* ```ts
Expand All @@ -57,8 +57,8 @@ export const hmacSha512 = (str: string | Buffer, key: string | Buffer): string =

/**
* Converts a base64 string to a Buffer for use with HMAC
* @param base64 - The base64 string to convert
* @returns Buffer
* @param {string} base64 - The base64 string to convert
* @returns {Buffer}
*
* @example
* ```ts
Expand All @@ -76,8 +76,8 @@ export const bufferFromBase64 = (base64: string): Buffer => {

/**
* Converts a hexadecimal string to a Buffer for use with HMAC
* @param hex - The hexadecimal string to convert
* @returns Buffer
* @param {string} hex - The hexadecimal string to convert
* @returns {Buffer}
*
* @example
* ```ts
Expand Down
2 changes: 1 addition & 1 deletion deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"tasks": {
"check": "deno fmt --check && deno lint && biome lint",
"test": "deno test --allow-sys --allow-env"
"test": "deno test --allow-sys --allow-env --clean --coverage"
},
"lint": {
"rules": {
Expand Down

0 comments on commit 1e71ba6

Please sign in to comment.