initial commit (b32, sha1, hmac)
This commit is contained in:
commit
226a728e82
BIN
manual.pdf
Normal file
BIN
manual.pdf
Normal file
Binary file not shown.
34
manual.typ
Normal file
34
manual.typ
Normal 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
62
src/base.typ
Normal 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
4
src/lib.typ
Normal file
@ -0,0 +1,4 @@
|
||||
#import "misc.typ": *
|
||||
#import "sha.typ"
|
||||
#import "utils.typ": *
|
||||
#import "base.typ": *
|
48
src/misc.typ
Normal file
48
src/misc.typ
Normal 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
115
src/sha.typ
Normal 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
100
src/utils.typ
Normal 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
14
typst.toml
Normal 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" ]
|
Loading…
Reference in New Issue
Block a user