diff --git a/crypto/README.md b/crypto/README.md index d9eab32..5202a59 100644 --- a/crypto/README.md +++ b/crypto/README.md @@ -12,12 +12,39 @@ A collection of cryptographic hash functions and utilities. ```moonbit let input = "The quick brown fox jumps over the lazy dog" -println(bytes_to_hex_string(sha1(input.to_bytes()))) // bd136cb58899c93173c33a90dde95ead0d0cf6df +println(bytes_to_hex_string(sha1(input.to_bytes()))) +// => bd136cb58899c93173c33a90dde95ead0d0cf6df ``` ### MD5 ```moonbit let input = "The quick brown fox jumps over the lazy dog" -println(bytes_to_hex_string(md5(input.to_bytes()))) // b0986ae6ee1eefee8a4a399090126837 +println(bytes_to_hex_string(md5(input.to_bytes()))) +// => b0986ae6ee1eefee8a4a399090126837 + +// buffered +let ctx = MD5Context::new() +ctx.update(b"a") +ctx.update(b"b") +ctx.update(b"c") +println(bytes_to_hex_string(ctx.finalize())) // or `ctx.compute()` +// => ce1473cf80c6b3fda8e3dfc006adc315 +``` + +### SM3 + +```moonbit +let input = "The quick brown fox jumps over the lazy dog" +println(bytes_to_hex_string(sm3(input.to_bytes()))) +// => fc2b31896629e88652ca1e3be449ec7ec93f7e5e29769f273fb973bc1858c66d + + +//buffered +let ctx = SM3Context::new() +ctx.update(b"a") +ctx.update(b"b") +ctx.update(b"c") +println(bytes_to_hex_string(ctx.finalize())) +// => 66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0 ``` diff --git a/crypto/crypto.mbti b/crypto/crypto.mbti index 67f33ba..72dc32e 100644 --- a/crypto/crypto.mbti +++ b/crypto/crypto.mbti @@ -1,12 +1,6 @@ package moonbitlang/x/crypto // Values -fn arr_u8_to_u32be(Array[Byte], ~i : Int = ..) -> UInt - -fn byte_array_to_bytes(Array[Byte]) -> Bytes - -fn bytes_to_byte_array(Bytes) -> Array[Byte] - fn bytes_to_hex_string(Bytes) -> String fn chacha12(FixedArray[UInt], UInt, Bytes, ~nonce : UInt = ..) -> Bytes! @@ -19,15 +13,27 @@ fn md5(Bytes) -> Bytes fn sha1(Bytes) -> Bytes -fn sm3(Bytes) -> Array[UInt] - -fn u8_to_u32be(Bytes, ~i : Int = ..) -> UInt +fn sm3(Bytes) -> Bytes -fn u8_to_u32le(Bytes, ~i : Int = ..) -> UInt +fn sm3_from_iter(Iter[Byte]) -> Bytes fn uints_to_hex_string(Array[UInt]) -> String // Types and methods +type MD5Context +impl MD5Context { + finalize(Self) -> Bytes + new() -> Self + update(Self, Bytes) -> Unit +} + +type SM3Context +impl SM3Context { + finalize(Self) -> Bytes + new() -> Self + update(Self, Bytes) -> Unit + update_from_iter(Self, Iter[Byte]) -> Unit +} // Type aliases diff --git a/crypto/md5.mbt b/crypto/md5.mbt index 93f5d36..7800af3 100644 --- a/crypto/md5.mbt +++ b/crypto/md5.mbt @@ -16,7 +16,7 @@ // [RFC1321] https://www.ietf.org/rfc/rfc1321.txt // [Ron Rivest] https://people.csail.mit.edu/rivest/Md5.c // [md5-0.7.0] https://docs.rs/md5/0.7.0/src/md5/lib.rs.html -priv struct MD5Context { +struct MD5Context { state : FixedArray[UInt] // state 'a' 'b' 'c' 'd' count : FixedArray[UInt] buffer : Bytes @@ -24,7 +24,18 @@ priv struct MD5Context { let padding : Bytes = Bytes::make(64, b'\x00') -fn MD5Context::make() -> MD5Context { +/// update the state of given context from new `data` +pub fn MD5Context::update(self : MD5Context, data : Bytes) -> Unit { + md5_update(self, data) +} + +/// an alias of `MD5Context::compute()` +pub fn MD5Context::finalize(self : MD5Context) -> Bytes { + self.md5_compute() +} + +/// Instantiate a MD5 context +pub fn MD5Context::new() -> MD5Context { padding[0] = b'\x80' { state: [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476], @@ -33,7 +44,8 @@ fn MD5Context::make() -> MD5Context { } } -fn MD5Context::compute(self : MD5Context) -> Bytes { +/// compute MD5 digest from given context +fn MD5Context::md5_compute(self : MD5Context) -> Bytes { let input = FixedArray::make(16, 0U) let idx = (self.count[0].lsr(3) & 0x3f).to_int() input[14] = self.count[0] @@ -220,9 +232,9 @@ fn md5_transform(state : FixedArray[UInt], input : FixedArray[UInt]) -> Unit { /// - Note that MD5 is considered _cryptographically broken_. /// Unless mandated, more secure alternatives should be preferred. pub fn md5(data : Bytes) -> Bytes { - let ctx = MD5Context::make() + let ctx = MD5Context::new() md5_update(ctx, data) - ctx.compute() + ctx.md5_compute() } test "md5_wb" { @@ -232,3 +244,15 @@ test "md5_wb" { content="b0986ae6ee1eefee8a4a399090126837", ) } + +test { + let ctx = MD5Context::new() + md5_update(ctx, b"\x61") + md5_update(ctx, b"\x62") + md5_update(ctx, b"\x63") + let res1 = bytes_to_hex_string(ctx.md5_compute()) + let ctx = MD5Context::new() + md5_update(ctx, b"\x61\x62\x63") + let res2 = bytes_to_hex_string(ctx.md5_compute()) + @test.eq!(res1, res2) +} diff --git a/crypto/sm3.mbt b/crypto/sm3.mbt index 93be557..7d26c51 100644 --- a/crypto/sm3.mbt +++ b/crypto/sm3.mbt @@ -16,20 +16,23 @@ // - [GM/T 0004-2012] https://www.oscca.gov.cn/sca/xxgk/2010-12/17/1002389/files/302a3ada057c4a73830536d03e683110.pdf // SM3 is as secure as SHA256, providing similar performance. https://doi.org/10.3390/electronics8091033 -priv struct SM3Context { +struct SM3Context { reg : FixedArray[UInt] // register A B C D E F G H. i.e. digest mut len : UInt64 - mut msg : Array[Byte] + mut buf : Bytes + mut buf_index : Int } -fn SM3Context::make() -> SM3Context { +/// Instantiate a SM3 context +pub fn SM3Context::new() -> SM3Context { { reg: [ 0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600, 0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E, ], len: 0, - msg: [], + buf: Bytes::new(64), + buf_index: 0, } } @@ -77,23 +80,32 @@ fn SM3Context::p_1(x : UInt) -> UInt { x ^ urotate_left(x, 15) ^ urotate_left(x, 23) } -fn pad(self : SM3Context) -> Array[Byte] { - self.msg.push(b'\x80') - while self.msg.length() % 64 != 56 { - self.msg.push(b'\x00') +fn pad(self : SM3Context) -> Bytes { + let mut cnt = self.buf_index + self.len += 8UL * cnt.to_int64().to_uint64() + self.buf[cnt] = b'\x80' + cnt += 1 + if cnt > 56 { + let temp_arr = Bytes::new(128) + temp_arr.blit(0, self.buf, 0, cnt) + self.buf = temp_arr } - self.msg.push(self.len.lsr(56).to_byte()) - self.msg.push(self.len.lsr(48).to_byte()) - self.msg.push(self.len.lsr(40).to_byte()) - self.msg.push(self.len.lsr(32).to_byte()) - self.msg.push(self.len.lsr(24).to_byte()) - self.msg.push(self.len.lsr(16).to_byte()) - self.msg.push(self.len.lsr(8).to_byte()) - self.msg.push(self.len.lsr(0).to_byte()) - return self.msg -} - -fn update(self : SM3Context, data : Ref[Array[Byte]]) -> Array[UInt] { + while cnt % 64 != 56 { + self.buf[cnt] = b'\x00' + cnt += 1 + } + self.buf[cnt] = self.len.lsr(56).to_byte() + self.buf[cnt + 1] = self.len.lsr(48).to_byte() + self.buf[cnt + 2] = self.len.lsr(40).to_byte() + self.buf[cnt + 3] = self.len.lsr(32).to_byte() + self.buf[cnt + 4] = self.len.lsr(24).to_byte() + self.buf[cnt + 5] = self.len.lsr(16).to_byte() + self.buf[cnt + 6] = self.len.lsr(8).to_byte() + self.buf[cnt + 7] = self.len.lsr(0).to_byte() + return self.buf +} + +fn transform(self : SM3Context, data : Bytes, ~offset : Int = 0) -> Array[UInt] { let w_0 = FixedArray::make(68, 0U) let w_1 = FixedArray::make(64, 0U) let mut a = self.reg[0] @@ -104,66 +116,61 @@ fn update(self : SM3Context, data : Ref[Array[Byte]]) -> Array[UInt] { let mut f = self.reg[5] let mut g = self.reg[6] let mut h = self.reg[7] - while data.val.length() >= 64 { - for index = 0; index < 16; index = index + 1 { - w_0[index] = arr_u8_to_u32be(data.val, i=4 * index) - } - for index = 16; index < 68; index = index + 1 { - w_0[index] = p_1( - w_0[index - 16] ^ w_0[index - 9] ^ urotate_left(w_0[index - 3], 15), - ) ^ urotate_left(w_0[index - 13], 7) ^ w_0[index - 6] - } - for index = 0; index < 64; index = index + 1 { - w_1[index] = w_0[index] ^ w_0[index + 4] - } - let mut a1 = a - let mut b1 = b - let mut c1 = c - let mut d1 = d - let mut e1 = e - let mut f1 = f - let mut g1 = g - let mut h1 = h - for index = 0; index < 16; index = index + 1 { - let ss_1 = urotate_left(urotate_left(a1, 12) + e1 + t[index], 7) - let ss_2 = ss_1 ^ urotate_left(a1, 12) - let tt_1 = ff_0(a1, b1, c1) + d1 + ss_2 + w_1[index] - let tt_2 = gg_0(e1, f1, g1) + h1 + ss_1 + w_0[index] - d1 = c1 - c1 = urotate_left(b1, 9) - b1 = a1 - a1 = tt_1 - h1 = g1 - g1 = urotate_left(f1, 19) - f1 = e1 - e1 = p_0(tt_2) - } - for index = 16; index < 64; index = index + 1 { - let ss_1 = urotate_left(urotate_left(a1, 12) + e1 + t[index], 7) - let ss_2 = ss_1 ^ urotate_left(a1, 12) - let tt_1 = ff_1(a1, b1, c1) + d1 + ss_2 + w_1[index] - let tt_2 = gg_1(e1, f1, g1) + h1 + ss_1 + w_0[index] - d1 = c1 - c1 = urotate_left(b1, 9) - b1 = a1 - a1 = tt_1 - h1 = g1 - g1 = urotate_left(f1, 19) - f1 = e1 - e1 = p_0(tt_2) - } - a = a ^ a1 - b = b ^ b1 - c = c ^ c1 - d = d ^ d1 - e = e ^ e1 - f = f ^ f1 - g = g ^ g1 - h = h ^ h1 - let t_arr = Array::make(data.val.length() - 64, b'\x00') - data.val.blit_to(t_arr, len=data.val.length() - 64, src_offset=64) - data.val = t_arr + for index = 0; index < 16; index = index + 1 { + w_0[index] = bytes_u8_to_u32be(data, i=4 * index + offset) + } + for index = 16; index < 68; index = index + 1 { + w_0[index] = p_1( + w_0[index - 16] ^ w_0[index - 9] ^ urotate_left(w_0[index - 3], 15), + ) ^ urotate_left(w_0[index - 13], 7) ^ w_0[index - 6] + } + for index = 0; index < 64; index = index + 1 { + w_1[index] = w_0[index] ^ w_0[index + 4] + } + let mut a1 = a + let mut b1 = b + let mut c1 = c + let mut d1 = d + let mut e1 = e + let mut f1 = f + let mut g1 = g + let mut h1 = h + for index = 0; index < 16; index = index + 1 { + let ss_1 = urotate_left(urotate_left(a1, 12) + e1 + t[index], 7) + let ss_2 = ss_1 ^ urotate_left(a1, 12) + let tt_1 = ff_0(a1, b1, c1) + d1 + ss_2 + w_1[index] + let tt_2 = gg_0(e1, f1, g1) + h1 + ss_1 + w_0[index] + d1 = c1 + c1 = urotate_left(b1, 9) + b1 = a1 + a1 = tt_1 + h1 = g1 + g1 = urotate_left(f1, 19) + f1 = e1 + e1 = p_0(tt_2) } + for index = 16; index < 64; index = index + 1 { + let ss_1 = urotate_left(urotate_left(a1, 12) + e1 + t[index], 7) + let ss_2 = ss_1 ^ urotate_left(a1, 12) + let tt_1 = ff_1(a1, b1, c1) + d1 + ss_2 + w_1[index] + let tt_2 = gg_1(e1, f1, g1) + h1 + ss_1 + w_0[index] + d1 = c1 + c1 = urotate_left(b1, 9) + b1 = a1 + a1 = tt_1 + h1 = g1 + g1 = urotate_left(f1, 19) + f1 = e1 + e1 = p_0(tt_2) + } + a = a ^ a1 + b = b ^ b1 + c = c ^ c1 + d = d ^ d1 + e = e ^ e1 + f = f ^ f1 + g = g ^ g1 + h = h ^ h1 let t_arr : Array[UInt] = [a, b, c, d, e, f, g, h] for index = 0; index < 8; index = index + 1 { self.reg[index] = t_arr[index] @@ -171,35 +178,73 @@ fn update(self : SM3Context, data : Ref[Array[Byte]]) -> Array[UInt] { return t_arr } -fn compute(self : SM3Context, data : Array[Byte]) -> Unit { - self.len += data.length().to_int64().to_uint64() * 8UL - let msg = self.msg + data - let block_num = msg.length() / 64 - let len = msg.length() - block_num * 64 - let _ = self.update(Ref::new(msg)) - let new_msg = Array::make(len, b'\x00') - msg.blit_to(new_msg, ~len, src_offset=block_num * 64) - self.msg = new_msg +fn sm3_update(self : SM3Context, data : Iter[Byte]) -> Unit { + data.each( + fn(b) { + self.buf[self.buf_index] = b + self.buf_index += 1 + if self.buf_index == 64 { + self.buf_index = 0 + self.len += 512UL + let _ = self.transform(self.buf) + + } + }, + ) +} + +/// update the state of given context from new `data` +pub fn update(self : SM3Context, data : Bytes) -> Unit { + self.sm3_update(bytes_to_iter(data)) } -fn sum(self : SM3Context, ~data : Array[Byte] = []) -> Array[UInt] { - self.compute(data) +pub fn update_from_iter(self : SM3Context, data : Iter[Byte]) -> Unit { + self.sm3_update(data) +} + +fn sm3_compute( + self : SM3Context, + ~data : Iter[Byte] = Iter::empty() +) -> Array[UInt] { + self.sm3_update(data) let msg = self.pad() - self.update(Ref::new(msg)) + if msg.length() > 64 { + let _ = self.transform(msg) + self.transform(msg, offset=64) + } else { + self.transform(msg) + } +} + +/// Compute the SM3 digest in `UInt[]` of some `data` +fn sm3_u32_from_iter(data : Iter[Byte]) -> Array[UInt] { + let ctx = SM3Context::new() + let _ = ctx.sm3_update(data) + ctx.sm3_compute() +} + +fn sm3_u32(data : Bytes) -> Array[UInt] { + sm3_u32_from_iter(bytes_to_iter(data)) } -/// Compute the SM3 digest of some `data` -pub fn sm3(data : Bytes) -> Array[UInt] { - let bytes_data = bytes_to_byte_array(data) - let ctx = SM3Context::make() - let _ = ctx.compute(bytes_data) - ctx.sum() +/// Compute the SM3 digest in `Bytes` of some `data`. Note that SM3 is big-endian. +pub fn sm3(data : Bytes) -> Bytes { + arr_u32_to_u8be(sm3_u32(data)) +} + +pub fn sm3_from_iter(data : Iter[Byte]) -> Bytes { + arr_u32_to_u8be(sm3_u32_from_iter(data)) +} + +/// an alias of `SM3Context::compute()` +pub fn finalize(self : SM3Context) -> Bytes { + arr_u32_to_u8be(self.sm3_compute()) } test { inspect!( uints_to_hex_string( - sm3( + sm3_u32( b"\x61\x62\x63", // abc in utf-8 ), ), @@ -207,11 +252,21 @@ test { ) inspect!( uints_to_hex_string( - sm3( + sm3_u32( // abcd * 16 in utf-8 b"\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64\x61\x62\x63\x64", ), ), content="debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732", ) + @test.eq!( + bytes_to_hex_string(sm3(b"\x61\x62\x63")), + uints_to_hex_string(sm3_u32(b"\x61\x62\x63")), + ) + let hash1 = "66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0" + let ctx = SM3Context::new() + ctx.update(b"\x61") + ctx.update(b"\x62") + ctx.update(b"\x63") + @test.eq!(hash1, bytes_to_hex_string(ctx.finalize())) } diff --git a/crypto/sm3_test.mbt b/crypto/sm3_test.mbt index d0a13c8..8fe9e63 100644 --- a/crypto/sm3_test.mbt +++ b/crypto/sm3_test.mbt @@ -13,7 +13,7 @@ // limitations under the License. fn sm3test(s : String) -> String { - @crypto.uints_to_hex_string(@crypto.sm3(s.to_bytes())) + @crypto.bytes_to_hex_string(@crypto.sm3(s.to_bytes())) } // testcases from GM/T 0004-2012 diff --git a/crypto/utils.mbt b/crypto/utils.mbt index d92e5a1..967a51f 100644 --- a/crypto/utils.mbt +++ b/crypto/utils.mbt @@ -18,6 +18,7 @@ let hex_digits : FixedArray[String] = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", ] +/// print a sequence of byte in hex representation pub fn bytes_to_hex_string(input : Bytes) -> String { let mut ret = "" for i = input.length() - 1; i >= 0; i = i - 1 { @@ -43,6 +44,7 @@ fn uint_to_hex_string(input : UInt) -> String { ret.fold_left(String::op_add, init="") } +/// print a sequence of uint in hex representation pub fn uints_to_hex_string(input : Array[UInt]) -> String { input.map(uint_to_hex_string).fold_left(String::op_add, init="") } @@ -55,44 +57,89 @@ fn uint32(x : Byte) -> UInt { x.to_int().to_uint() } -pub fn u8_to_u32le(x : Bytes, ~i : Int = 0) -> UInt { +/// convert 4 bytes a byte sequence to a UInt in little endian +/// - `x` : A byte sequence consisting of 4 or more bytes +/// - `i` : An offset. e.g. i = 4 will convert `x[4~7]` to a UInt. +fn u8_to_u32le(x : Bytes, ~i : Int = 0) -> UInt { uint32(x[i]) | uint32(x[i + 1]).lsl(8) | uint32(x[i + 2]).lsl(16) | uint32( x[i + 3], ).lsl(24) } -pub fn u8_to_u32be(x : Bytes, ~i : Int = 0) -> UInt { - uint32(x[i]).lsl(24) | uint32(x[i + 1]).lsl(16) | uint32(x[i + 2]).lsl(8) | uint32( - x[i + 3], - ) -} +/// convert 4 bytes of a byte sequence to a UInt in big endian +/// - `x` : A byte sequence consisting of 4 or more bytes +/// - `i` : An offset. e.g. i = 4 will convert `x[4~7]` to a UInt. -pub fn arr_u8_to_u32be(x : Array[Byte], ~i : Int = 0) -> UInt { +// fn u8_to_u32be(x : Bytes, ~i : Int = 0) -> UInt { +// uint32(x[i]).lsl(24) | uint32(x[i + 1]).lsl(16) | uint32(x[i + 2]).lsl(8) | uint32( +// x[i + 3], +// ) +// } + +/// convert 4 bytes of a byte array to a UInt in big endian +/// - `x` : A byte sequence consisting of 4 or more bytes +/// - `i` : An offset. e.g. i = 4 will convert `x[4~7]` to a UInt. + +// fn arr_u8_to_u32be(x : Array[Byte], ~i : Int = 0) -> UInt { +// uint32(x[i]).lsl(24) | uint32(x[i + 1]).lsl(16) | uint32(x[i + 2]).lsl(8) | uint32( +// x[i + 3], +// ) +// } + +fn bytes_u8_to_u32be(x : Bytes, ~i : Int = 0) -> UInt { uint32(x[i]).lsl(24) | uint32(x[i + 1]).lsl(16) | uint32(x[i + 2]).lsl(8) | uint32( x[i + 3], ) } -pub fn byte_array_to_bytes(s : Array[Byte]) -> Bytes { - let bytes = Bytes::make(s.length(), b'\x00') - for index = 0; index < s.length(); index = index + 1 { - bytes[index] = s[index] - } - bytes +/// convert a UInt to 4 bytes + +fn u32_to_u8be(x : UInt) -> Array[Byte] { + let b = Array::make(4, b'\x00') + b[0] = x.lsr(24).to_byte() + b[1] = x.lsr(16).to_byte() + b[2] = x.lsr(8).to_byte() + b[3] = x.to_byte() + b } -pub fn bytes_to_byte_array(s : Bytes) -> Array[Byte] { - let byte_array = Array::make(s.length(), b'\x00') - for index = 0; index < s.length(); index = index + 1 { - byte_array[index] = s[index] +/// convert an array of UInt to Bytes in big endian + +fn arr_u32_to_u8be(x : Array[UInt]) -> Bytes { + let temp : Bytes = Bytes::make(x.length() * 4, b'\x00') + for index = 0; index < x.length(); index = index + 1 { + let u8 = u32_to_u8be(x[index]) + temp[index * 4] = u8[0] + temp[index * 4 + 1] = u8[1] + temp[index * 4 + 2] = u8[2] + temp[index * 4 + 3] = u8[3] } - byte_array + temp } +/// rotate a Int `x` left by `n` bit(s) + fn rotate_left(x : Int, n : Int) -> Int { x.lsl(n).lor(x.lsr(32 - n)) } +/// rotate a UInt `x` left by `n` bit(s) fn rotate_left_u(x : UInt, n : Int) -> UInt { x.lsl(n) | x.lsr(32 - n) } + +/// temp function for convert bytes_to_iter in absence of `Bytes::iter()`. + +fn bytes_to_iter(data : Bytes, ~length : Int = data.length()) -> Iter[Byte] { + Iter::new( + fn(yield) { + for i = 0, len = length; i < len; i = i + 1 { + if yield(data[i]) == IterEnd { + break IterEnd + } + } else { + IterContinue + } + }, + ) +}