Skip to content

Commit

Permalink
Merge pull request #7 from frytg/dev/hash-utils
Browse files Browse the repository at this point in the history
feat: add hash + hmac utils
  • Loading branch information
frytg authored Dec 16, 2024
2 parents f16a910 + 052c297 commit 0ee6ae0
Show file tree
Hide file tree
Showing 15 changed files with 263 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ permissions:
contents: read

env:
JSR_DEPENDENCIES: "@cross/test @std/assert @std/fmt @frytg/logger @frytg/dates"
JSR_DEPENDENCIES: "@cross/test @std/assert @std/fmt @frytg/logger"
NPM_DEPENDENCIES: "luxon sinon"

jobs:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ node_modules/
vendor/

tmp
.dev/
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ This repository is work in progress.
- [`@frytg/dates`](./dates/README.md) - Date utilities around Luxon
- [`@frytg/logger`](./logger/README.md) - Pre-configuredWinston logging wrapper

## More Tooling

Planned for this utility package:

- `hashes` - sha256, sha512, etc.

Other tools that I regularly use and don't feel the need to optimize or re-create in this utility package:

- [`axios`](https://github.com/axios/axios) - _Promise based HTTP client for the browser and node.js_
- [`hono`](https://jsr.io/@hono/hono) - _small, simple, and ultrafast web framework built on Web Standards_
- [`undici`](https://github.com/nodejs/undici) - performant HTTP/1.1 client for Node.js

## Lint

Use `deno fmt`, `deno lint` and `biome lint` to check the code.
Expand Down Expand Up @@ -43,13 +55,6 @@ deno publish --dry-run
Then once everything is pushed or merged on `main`, run the GitHub actions workflow to publish the packages to JSR
(see [_publishing packages_](https://jsr.io/docs/publishing-packages) for more details).

## More Tooling

Other tools that I regularly use and don't feel the need to optimize or re-create in this utility package:

- [`axios`](https://github.com/axios/axios) - _Promise based HTTP client for the browser and node.js_
- [`hono`](https://jsr.io/@hono/hono) - _small, simple, and ultrafast web framework built on Web Standards_

## Author

Created by [@frytg](https://github.com/frytg) / [frytg.digital](https://www.frytg.digital)
Expand Down
2 changes: 1 addition & 1 deletion check-required-env/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Simply check if a certain required environment variable is set. If not, throw an error and exit the process.

```ts
import { checkRequiredEnv } from 'jsr:@frytg/check-required-env';
import { checkRequiredEnv } from '@frytg/check-required-env';

checkRequiredEnv('MY_IMPORTANT_ENV_VAR');
```
Expand Down
2 changes: 1 addition & 1 deletion check-required-env/check-required-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import logger from '@frytg/logger'
*
* @example
* ```ts
* import { checkRequiredEnv } from 'jsr:@frytg/check-required-env'
* import { checkRequiredEnv } from '@frytg/check-required-env'
*
* checkRequiredEnv('MY_IMPORTANT_ENV_VAR')
* ```
Expand Down
5 changes: 5 additions & 0 deletions crypto/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Crypto Changelog

## 2024-12-12 - 0.0.1

- feat: added hash and hmac functions for SHA-256 and SHA-512
17 changes: 17 additions & 0 deletions crypto/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Crypto utilities

[![JSR @frytg/crypto](https://jsr.io/badges/@frytg/crypto)](https://jsr.io/@frytg/crypto)
[![ci](https://github.com/frytg/utility/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/frytg/utility/actions/workflows/test.yml)

This package provides functions to compute SHA-256 and SHA-512 hashes using [native Node crypto](https://nodejs.org/api/crypto.html).

- [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)

## Author

Created by [@frytg](https://github.com/frytg) / [frytg.digital](https://www.frytg.digital)

## License

[Unlicense](https://github.com/frytg/utility/blob/main/LICENSE) - also see [unlicense.org](https://unlicense.org)
49 changes: 49 additions & 0 deletions crypto/hash.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { test } from '@cross/test'
import { assertEquals } from '@std/assert'

import { hashSha256, hashSha512 } from './hash.ts'

test('hashSha256 - generates correct SHA-256 hashes', () => {
const testCases = [
{
input: 'hello',
expected: '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
},
{
input: '',
expected: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
},
{
input: 'The quick brown fox jumps over the lazy dog',
expected: 'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592',
},
]

for (const { input, expected } of testCases) {
assertEquals(hashSha256(input), expected, `hashSha256("${input}") should return "${expected}"`)
}
})

test('hashSha512 - generates correct SHA-512 hashes', () => {
const testCases = [
{
input: 'hello',
expected:
'9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043',
},
{
input: '',
expected:
'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e',
},
{
input: 'The quick brown fox jumps over the lazy dog',
expected:
'07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6',
},
]

for (const { input, expected } of testCases) {
assertEquals(hashSha512(input), expected, `hashSha512("${input}") should return "${expected}"`)
}
})
31 changes: 31 additions & 0 deletions crypto/hash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createHash } from 'node:crypto'

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)
*
* @example SHA-256
* ```ts
* import { hashSha256 } from '@frytg/crypto/hash'
*
* hashSha256('hello')
* ```
*/
export const hashSha256 = (str: string): string => createHash('sha256').update(str).digest(HEX_ENCODING)

/**
* 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)
*
* @example SHA-512
* ```ts
* import { hashSha512 } from '@frytg/crypto/hash'
*
* hashSha512('hello')
* ```
*/
export const hashSha512 = (str: string): string => createHash('sha512').update(str).digest(HEX_ENCODING)
77 changes: 77 additions & 0 deletions crypto/hmac.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Buffer } from 'node:buffer'
import { test } from '@cross/test'
import { assertEquals } from '@std/assert'

import { bufferFromHex, hmacSha256, hmacSha512 } from './hmac.ts'

test('hmacSha256 - generates correct HMAC SHA-256 hashes', () => {
const testCases = [
{
input: 'hello',
key: 'secret',
expected: '88aab3ede8d3adf94d26ab90d3bafd4a2083070c3bcce9c014ee04a443847c0b',
},
{
input: '',
key: 'secret',
expected: 'f9e66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169',
},
{
input: 'The quick brown fox jumps over the lazy dog',
key: 'secret',
expected: '54cd5b827c0ec938fa072a29b177469c843317b095591dc846767aa338bac600',
},
]

for (const { input, key, expected } of testCases) {
assertEquals(hmacSha256(input, key), expected, `hmacSha256("${input}", "${key}") should return "${expected}"`)
}
})

test('hmacSha512 - generates correct HMAC SHA-512 hashes', () => {
const testCases = [
{
input: 'hello',
key: 'secret',
expected:
'db1595ae88a62fd151ec1cba81b98c39df82daae7b4cb9820f446d5bf02f1dcfca6683d88cab3e273f5963ab8ec469a746b5b19086371239f67d1e5f99a79440',
},
{
input: '',
key: 'secret',
expected:
'b0e9650c5faf9cd8ae02276671545424104589b3656731ec193b25d01b07561c27637c2d4d68389d6cf5007a8632c26ec89ba80a01c77a6cdd389ec28db43901',
},
{
input: 'The quick brown fox jumps over the lazy dog',
key: 'secret',
expected:
'76af3588620ef6e2c244d5a360e080c0d649b6dd6b82ccd115eeefee8ff403bcee9aeb08618db9a2a94a9e80c7996bb2cb0c00f6e69de38ed8af2758ef39df0a',
},
]

for (const { input, key, expected } of testCases) {
assertEquals(hmacSha512(input, key), expected, `hmacSha512("${input}", "${key}") should return "${expected}"`)
}
})

test('bufferFromHex - converts hex strings to Buffer correctly', () => {
const testCases = [
{
input: '0123456789abcdef',
expected: Buffer.from([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]),
},
{
input: 'ff00ff00',
expected: Buffer.from([0xff, 0x00, 0xff, 0x00]),
},
{
input: '',
expected: Buffer.from([]),
},
]

for (const { input, expected } of testCases) {
assertEquals(bufferFromHex(input), expected, `bufferFromHex("${input}") should return correct Buffer`)
}
})
51 changes: 51 additions & 0 deletions crypto/hmac.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// load package
import { Buffer } from 'node:buffer'
import { createHmac } from 'node:crypto'

const HEX_ENCODING = 'hex'

/**
* 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
* @returns HMAC SHA-256 hash of the input string (hexadecimal)
*
* @example
* ```ts
* import { hmacSha256 } from '@frytg/crypto/hmac'
*
* hmacSha256('hello', 'my-secret-key')
* ```
*/
export const hmacSha256 = (str: string | Buffer, key: string | Buffer): string =>
createHmac('sha256', key).update(str).digest(HEX_ENCODING)

/**
* 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
* @returns HMAC SHA-512 hash of the input string (hexadecimal)
*
* @example
* ```ts
* import { hmacSha512 } from '@frytg/crypto/hmac'
*
* hmacSha512('hello', 'my-secret-key')
* ```
*/
export const hmacSha512 = (str: string | Buffer, key: string | Buffer): string =>
createHmac('sha512', key).update(str).digest(HEX_ENCODING)

/**
* Converts a hexadecimal string to a Buffer for use with HMAC
* @param hex - The hexadecimal string to convert
* @returns Buffer
*
* @example
* ```ts
* import { hmacSha512, bufferFromHex } from '@frytg/crypto/hmac'
*
* hmacSha512('hello', bufferFromHex('0123456789abcdef'))
* ```
*/
export const bufferFromHex = (hex: string): Buffer => Buffer.from(hex, HEX_ENCODING)
12 changes: 12 additions & 0 deletions crypto/jsr.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "https://jsr.io/schema/config-file.v1.json",
"name": "@frytg/crypto",
"version": "0.0.1",
"exports": {
"hash": "./hash.ts",
"hmac": "./hmac.ts"
},
"publish": {
"exclude": ["*.test.ts"]
}
}
3 changes: 3 additions & 0 deletions dates/deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@
"imports": {
"@std/fmt": "jsr:@std/fmt@^1.0.3",
"luxon": "npm:luxon@^3.5.0"
},
"publish": {
"exclude": ["*.test.ts"]
}
}
2 changes: 1 addition & 1 deletion logger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Debug logs will only be logged if the env `STAGE` is set to `dev`.
## Usage

```ts
import logger from 'jsr:@frytg/logger';
import logger from '@frytg/logger';
```

```ts
Expand Down
2 changes: 1 addition & 1 deletion logger/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const formatConfigLocal: Logform.Format = format.combine(
*
* @example basic log
* ```ts
* import { logger } from 'jsr:@frytg/logger'
* import { logger } from '@frytg/logger'
*
* logger.log({
* level: 'debug',
Expand Down

0 comments on commit 0ee6ae0

Please sign in to comment.