initial commit (b32, sha1, hmac)

This commit is contained in:
Louis Heredero 2024-12-21 14:17:09 +01:00
commit 226a728e82
Signed by: HEL
GPG Key ID: 8D83DE470F8544E7
8 changed files with 377 additions and 0 deletions

BIN
manual.pdf Normal file

Binary file not shown.

34
manual.typ Normal file
View File

@ -0,0 +1,34 @@
#import "@preview/tidy:0.4.0"
#import "src/lib.typ"
#let mod = tidy.parse-module.with(
scope: (jumble: lib),
preamble: "#import jumble: *;"
)
#let sha-doc = mod(
read("src/sha.typ"),
name: "sha"
)
#tidy.show-module(sha-doc)
#let misc-doc = mod(
read("src/misc.typ"),
name: "misc"
)
#tidy.show-module(misc-doc)
#let base-doc = mod(
read("src/base.typ"),
name: "base"
)
#tidy.show-module(base-doc)
#pagebreak()
#let utils-doc = mod(
read("src/utils.typ"),
name: "utils"
)
#tidy.show-module(utils-doc)

62
src/base.typ Normal file
View File

@ -0,0 +1,62 @@
#import "utils.typ": z-fill, bin-to-int
#let b32-alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
/// Decodes a base32-encoded value
/// #test(
/// `jumble.b32-decode("LFHVKUCJ") == bytes("YOUPI")`
/// )
/// ```example
/// #str(b32-decode("LFHVKUCJ"))
/// ```
/// -> bytes
#let b32-decode(
/// -> str
encoded
) = {
let decoded-bin = ()
for char in encoded {
if char == "=" {
break
}
let i = b32-alphabet.position(char)
decoded-bin += z-fill(str(i, base: 2), 5).clusters().map(int)
}
decoded-bin = decoded-bin.slice(
0,
calc.div-euclid(decoded-bin.len(), 8) * 8
)
let decoded-bytes = decoded-bin.chunks(8).map(bin-to-int)
return bytes(decoded-bytes)
}
/// Encodes a value in base32
/// #test(
/// `jumble.b32-encode(bytes("YOUPI")) == "LFHVKUCJ"`
/// )
/// ```example
/// #b32-encode(bytes("YOUPI"))
/// ```
/// -> str
#let b32-encode(
/// -> bytes
decoded
) = {
let encoded = ""
let decoded-bin = array(decoded).map(b => z-fill(str(b, base: 2), 8)).join()
decoded-bin = decoded-bin.clusters().map(int)
let groups = decoded-bin.chunks(40)
for group in groups {
let chars = group.chunks(5)
if chars.last().len() != 5 {
chars.last() += (0,) * (5 - chars.last().len())
}
let chars = chars.map(bin-to-int)
.map(c => b32-alphabet.at(c))
encoded += chars.join()
let pad = 8 - chars.len()
if pad != 0 {
encoded += "=" * pad
}
}
return encoded
}

4
src/lib.typ Normal file
View File

@ -0,0 +1,4 @@
#import "misc.typ": *
#import "sha.typ"
#import "utils.typ": *
#import "base.typ": *

48
src/misc.typ Normal file
View File

@ -0,0 +1,48 @@
#import "utils.typ": xor-bytes
#import "sha.typ": sha1
#let _compute-block-sized-key(key, hash-func: sha1, block-size: 64) = {
if key.len() > block-size {
key = hash-func(key)
}
if key.len() < block-size {
key = bytes((0,) * (block-size - key.len())) + key
}
return key
}
/// Hash-based Message Authentication Code
/// ```example
/// #bytes-to-hex(hmac("Key", "Hello World!"))
/// ```
/// -> bytes
#let hmac(
/// Hashing key
/// -> str | bytes
key,
/// Message to hash
/// -> str | bytes
message,
/// Hashing function
/// -> function
hash-func: sha1,
/// Block size
/// -> number
block-size: 64
) = {
let key = if type(key) == str {bytes(key)} else {key}
let message = if type(message) == str {bytes(message)} else {message}
assert(type(key) == bytes, message: "key must be a string or bytes, but is " + repr(type(key)))
assert(type(message) == bytes, message: "message must be a string or bytes, but is " + repr(type(message)))
let block-sized-key = _compute-block-sized-key(key, hash-func: hash-func, block-size: block-size)
let i-pad = bytes((0x36,) * block-size)
let o-pad = bytes((0x5c,) * block-size)
let i-key-pad = xor-bytes(key, i-pad)
let o-key-pad = xor-bytes(key, o-pad)
return hash-func(o-key-pad + hash-func(i-key-pad + message))
}

115
src/sha.typ Normal file
View File

@ -0,0 +1,115 @@
#import "utils.typ": *
#let sha1-default-iv = (
0x67452301,
0xefcdab89,
0x98badcfe,
0x10325476,
0xc3d2e1f0
)
#let _sha1-const(i) = {
if i < 20 {
return 0x5a827999
} else if i < 40 {
return 0x6ed9eba1
} else if i < 60 {
return 0x8f1bbcdc
} else {
return 0xca62c1d6
}
}
#let _sha1-func(i, b, c, d) = {
if i < 20 {
return b.bit-and(c).bit-or(b.bit-not().bit-and(mask-32).bit-and(d))
} else if i < 40 {
return b.bit-xor(c).bit-xor(d)
} else if i < 60 {
let bc = b.bit-and(c)
let bd = b.bit-and(d)
let cd = c.bit-and(d)
return bc.bit-or(bd).bit-or(cd)
} else {
return b.bit-xor(c).bit-xor(d)
}
}
/// Secure Hash Algorithm 1
/// ```example
/// #bytes-to-hex(sha1("Hello World!"))
/// ```
/// -> bytes
#let sha1(
/// Message to hash
/// -> str
message,
/// Initial vector
/// -> array
iv: sha1-default-iv
) = {
// Complete message to multiple of 512 bits
let bin-str = ""
for char in bytes(message) {
bin-str += z-fill(str(char, base: 2), 8)
}
let l = bin-str.len()
bin-str += "1"
let padding = calc.rem-euclid(448 - bin-str.len(), 512)
if padding != 0 {
bin-str += "0" * padding
}
bin-str += z-fill(str(l, base: 2), 64)
let bin = bin-str.clusters().map(int)
// Split into blocks of 16 32-bit words
let words = bin.chunks(32).map(bin-to-int)
let blocks = words.chunks(16)
let vec = iv
for block in blocks {
// Expand
for i in range(16, 80) {
let chosen-words = (
block.at(i - 3),
block.at(i - 8),
block.at(i - 14),
block.at(i - 16)
)
let word = circular-shift(
chosen-words.fold(0, (a, b) => a.bit-xor(b))
)
block.push(word)
}
// Compress
let (A, B, C, D, E) = vec
for i in range(80) {
let temp = (
circular-shift(A, n: 5) +
_sha1-func(i, B, C, D) +
E +
block.at(i) +
_sha1-const(i)
)
temp = calc.rem(temp, max-32)
(A, B, C, D, E) = (temp, A, circular-shift(B, n: 30), C, D)
}
vec = vec.zip((A, B, C, D, E)).map(p => {
calc.rem(p.sum(), max-32)
})
}
let digest-bytes = ()
for n in vec {
digest-bytes += (
n.bit-rshift(24),
n.bit-rshift(16).bit-and(0xff),
n.bit-rshift(8).bit-and(0xff),
n.bit-and(0xff)
)
}
return bytes(digest-bytes)
}

100
src/utils.typ Normal file
View File

@ -0,0 +1,100 @@
/// Applies the XOR operation between two byte arrays
/// ```example
/// #let a = bytes((0b010, 0b0111))
/// #let b = bytes((0b011, 0b0101))
/// #array(xor-bytes(a, b)).map(
/// b => z-fill(str(b, base: 2), 3)
/// )
/// ```
/// -> bytes
#let xor-bytes(
/// First byte array
/// -> bytes
bytes-a,
/// Second byte array
/// -> bytes
bytes-b
) = {
let length = calc.max(bytes-a.len(), bytes-b.len())
let bytes-c = ()
for i in range(length) {
bytes-c.push(
bytes-a.at(i, default: 0).bit-xor(bytes-b.at(i, default: 0))
)
}
return bytes(bytes-c)
}
/// Pads a string with 0s on the left to reach a certain length
/// ```example
/// #z-fill("1011", 8)
/// ```
/// -> str
#let z-fill(
/// -> str
string,
/// -> number
length
) = {
return "0" * (length - string.len()) + string
}
/// Converts a byte array to a hexadecimal string
/// ```example
/// #let b = bytes((0xfa, 0xca, 0xde))
/// #bytes-to-hex(b)
/// ```
/// -> str
#let bytes-to-hex(
/// -> bytes
bytes
) = {
let res = ""
for byte in bytes {
res += z-fill(str(byte, base: 16), 2)
}
return res
}
/// Converts an array of bits into an integer
/// ```example
/// #let bits = (0, 0, 1, 0, 1, 0, 1, 0)
/// #bin-to-int(bits)
/// ```
/// -> number
#let bin-to-int(
/// Bit array
/// -> array
bin
) = {
return bin.fold(
0,
(v, b) => v.bit-lshift(1).bit-or(b)
)
}
#let max-32 = 1.bit-lshift(32)
#let mask-32 = max-32 - 1
/// Rotates a number to the left (wrapping the leftmost bits to the right)
/// ```example
/// #let a = 42
/// #let b = circular-shift(a, n: 20)
/// #let c = circular-shift(b, n: 11)
/// #b, #c
/// ```
/// -> number
#let circular-shift(
/// Number to rotate
/// -> number
x,
/// Shift amount
/// -> number
n: 1
) = {
if n < 0 {
return circular-shift(x, n: 32 + n)
}
let high-bits = x.bit-rshift(32 - n)
return x.bit-lshift(n).bit-or(high-bits).bit-and(mask-32)
}

14
typst.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "jumble"
version = "0.0.1"
compiler = "0.12.0"
repository = "https://git.kb28.ch/HEL/jumble"
entrypoint = "src/lib.typ"
authors = [
"Louis Heredero <https://git.kb28.ch/HEL>"
]
categories = ["scripting", "utility"]
license = "Apache-2.0"
description = "A package to hash "
keywords = ["hash", "algorithm", "cryptography", "md5", "sha1"]
exclude = [ "gallery", "gallery.bash", "docs" ]