initial commit (b32, sha1, hmac)
This commit is contained in:
		
							
								
								
									
										
											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" ] | ||||||
		Reference in New Issue
	
	Block a user