From c1d1efb2091e989dbb09e38b308c82c2cade1aa8 Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Mon, 29 Sep 2025 20:11:46 +0800 Subject: [PATCH] x.crypto.sm4: fix cbc API, make iv immutable (fix #25406) (#25416) --- vlib/x/crypto/sm4/sm4.v | 16 ++- vlib/x/crypto/sm4/sm4_test.v | 262 +++++++++++++++-------------------- 2 files changed, 126 insertions(+), 152 deletions(-) diff --git a/vlib/x/crypto/sm4/sm4.v b/vlib/x/crypto/sm4/sm4.v index 878981788..3c4c98ef8 100644 --- a/vlib/x/crypto/sm4/sm4.v +++ b/vlib/x/crypto/sm4/sm4.v @@ -399,8 +399,11 @@ pub fn (c &SM4Cipher) crypt_ecb(input []u8, mut output []u8) ! { // `iv` is a 16 bytes Initialization Vector. // `input` must be padded to multiple of 16 bytes. // `output` must be exactly the same length as `input`. +// returns: +// Updated IV ([]u8): The last ciphertext block for subsequent operations +// Error: If input length is invalid, IV length incorrect, or buffer mismatch @[direct_array_access] -pub fn (c &SM4Cipher) crypt_cbc(mut iv []u8, input []u8, mut output []u8) ! { +pub fn (c &SM4Cipher) crypt_cbc(iv []u8, input []u8, mut output []u8) ![]u8 { mut idx := 0 mut length := input.len if length & 0x0f != 0 || length == 0 { @@ -416,17 +419,19 @@ pub fn (c &SM4Cipher) crypt_cbc(mut iv []u8, input []u8, mut output []u8) ! { // fixed size array, avoid alloc on heap mut input_16 := [16]u8{} mut output_16 := [16]u8{} + mut iv_buf := [16]u8{} + unsafe { vmemcpy(&iv_buf[0], &iv[0], 16) } match c.mode { .sm4_encrypt { for length > 0 { // processing a 128bit block for i in 0 .. 16 { - input_16[i] = input[idx + i] ^ iv[i] // convert to fixed size array + input_16[i] = input[idx + i] ^ iv_buf[i] // convert to fixed size array } // use round keys encrypt the input_16 one_round(c.rk, input_16, mut output_16) - unsafe { vmemcpy(&iv[0], &output_16, 16) } // update iv with output + unsafe { vmemcpy(&iv_buf[0], &output_16, 16) } // update iv_buf with output unsafe { vmemcpy(&output[idx], &output_16, 16) } // convert from fixed size array idx += 16 length -= 16 @@ -439,12 +444,13 @@ pub fn (c &SM4Cipher) crypt_cbc(mut iv []u8, input []u8, mut output []u8) ! { // use round keys decrypt the input_16 one_round(c.rk, input_16, mut output_16) for i in 0 .. 16 { - output[idx + i] = output_16[i] ^ iv[i] // update output with iv - iv[i] = input_16[i] // update iv with input + output[idx + i] = output_16[i] ^ iv_buf[i] // update output with iv_buf + iv_buf[i] = input_16[i] // update iv_buf with input } idx += 16 length -= 16 } } } + return iv_buf[..] } diff --git a/vlib/x/crypto/sm4/sm4_test.v b/vlib/x/crypto/sm4/sm4_test.v index adc26f60c..7e69eaed4 100644 --- a/vlib/x/crypto/sm4/sm4_test.v +++ b/vlib/x/crypto/sm4/sm4_test.v @@ -1,152 +1,120 @@ // online tester: https://lzltool.cn/SM4 import x.crypto.sm4 -fn test_sm4_ecb() ! { - mut key := [u8(0x01), 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, - 0x54, 0x32, 0x10] - mut input := [u8(0x01), 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, - 0x54, 0x32, 0x10] - mut output := []u8{len: 16} - - // key = 0123456789abcdeffedcba9876543210 - // plaintext = 0123456789abcdeffedcba9876543210 - // ciphertext = 681edf34d206965e86b3e94f536e4246 - mut c1 := sm4.new_cipher(.sm4_encrypt, key)! - c1.crypt_ecb(input, mut output)! - assert output.hex() == '681edf34d206965e86b3e94f536e4246' - mut d1 := sm4.new_cipher(.sm4_decrypt, key)! - d1.crypt_ecb(output, mut output)! - assert output.hex() == '0123456789abcdeffedcba9876543210' - - // key = 0123456789abcdeffedcba9876543210 - // plaintext = 00112233445566778899aabbccddeeff - // ciphertext = 09325c4853832dcb9337a5984f671b9a - key = [u8(0x01), 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, - 0x32, 0x10] - input = [u8(0x00), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, - 0xdd, 0xee, 0xff] - output = []u8{len: 16} - mut c2 := sm4.new_cipher(.sm4_encrypt, key)! - c2.crypt_ecb(input, mut output)! - assert output.hex() == '09325c4853832dcb9337a5984f671b9a' - mut d2 := sm4.new_cipher(.sm4_decrypt, key)! - d2.crypt_ecb(output, mut output)! - assert output.hex() == '00112233445566778899aabbccddeeff' - - // key = 456789abcdeffedcba98765432100123 - // plaintext = 2233445566778899aabbccddeeff0011 - // ciphertext = 58ab414d84fb3008b0bee987f97021e6 - key = [u8(0x45), 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, - 0x01, 0x23] - input = [u8(0x22), 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, - 0xff, 0x00, 0x11] - output = []u8{len: 16} - mut c3 := sm4.new_cipher(.sm4_encrypt, key)! - c3.crypt_ecb(input, mut output)! - assert output.hex() == '58ab414d84fb3008b0bee987f97021e6' - mut d3 := sm4.new_cipher(.sm4_decrypt, key)! - d3.crypt_ecb(output, mut output)! - assert output.hex() == '2233445566778899aabbccddeeff0011' - - // key = 89abcdeffedcba987654321001234567 - // plaintext = 445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233 - // ciphertext = 5937a929a2d9137216c72a28cd9cf6195937a929a2d9137216c72a28cd9cf619 - key = [u8(0x89), 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x01, 0x23, - 0x45, 0x67] - input = [u8(0x44), 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, - 0x00, 0x11, 0x22, 0x33] - output = []u8{len: 32} - mut c4 := sm4.new_cipher(.sm4_encrypt, key)! - c4.crypt_ecb(input, mut output)! - assert output.hex() == '5937a929a2d9137216c72a28cd9cf6195937a929a2d9137216c72a28cd9cf619' - mut d4 := sm4.new_cipher(.sm4_decrypt, key)! - d4.crypt_ecb(output, mut output)! - assert output.hex() == '445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233' +struct SM4Data { + key string + iv string + input string + output string } -fn test_sm4_cbc() ! { - mut key := [u8(0x01), 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, - 0x54, 0x32, 0x10] - mut iv := [u8(0x01), 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, - 0x54, 0x32, 0x10] - mut orig_iv := iv.clone() - mut input := [u8(0x01), 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, - 0x54, 0x32, 0x10] - mut output := []u8{len: 16} - - // key = 0123456789abcdeffedcba9876543210 - // iv = 0123456789abcdeffedcba9876543210 - // plaintext = 0123456789abcdeffedcba9876543210 - // ciphertext = 2677f46b09c122cc975533105bd4a22a - mut c1 := sm4.new_cipher(.sm4_encrypt, key)! - c1.crypt_cbc(mut iv, input, mut output)! - assert output.hex() == '2677f46b09c122cc975533105bd4a22a' - iv = orig_iv.clone() - mut d1 := sm4.new_cipher(.sm4_decrypt, key)! - d1.crypt_cbc(mut iv, output, mut output)! - assert output.hex() == '0123456789abcdeffedcba9876543210' - - // key = 0123456789abcdeffedcba9876543210 - // iv = 112233445566778899aabbccddeeff00 - // plaintext = 00112233445566778899aabbccddeeff - // ciphertext = c3c0fffdcaac88ed63672b2b28a84e80 - key = [u8(0x01), 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, - 0x32, 0x10] - iv = [u8(0x11), 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, - 0xff, 0x00] - orig_iv = iv.clone() - input = [u8(0x00), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, - 0xdd, 0xee, 0xff] - output = []u8{len: 16} - mut c2 := sm4.new_cipher(.sm4_encrypt, key)! - c2.crypt_cbc(mut iv, input, mut output)! - assert output.hex() == 'c3c0fffdcaac88ed63672b2b28a84e80' - iv = orig_iv.clone() - mut d2 := sm4.new_cipher(.sm4_decrypt, key)! - d2.crypt_cbc(mut iv, output, mut output)! - assert output.hex() == '00112233445566778899aabbccddeeff' - - // key = 456789abcdeffedcba98765432100123 - // iv = 33445566778899aabbccddeeff001122 - // plaintext = 2233445566778899aabbccddeeff0011 - // ciphertext = 63dfbfc357c2e040b529c692ec916c6b - key = [u8(0x45), 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, - 0x01, 0x23] - iv = [u8(0x33), 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, - 0x11, 0x22] - orig_iv = iv.clone() - input = [u8(0x22), 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, - 0xff, 0x00, 0x11] - output = []u8{len: 16} - mut c3 := sm4.new_cipher(.sm4_encrypt, key)! - c3.crypt_cbc(mut iv, input, mut output)! - assert output.hex() == '63dfbfc357c2e040b529c692ec916c6b' - iv = orig_iv.clone() - mut d3 := sm4.new_cipher(.sm4_decrypt, key)! - d3.crypt_cbc(mut iv, output, mut output)! - assert output.hex() == '2233445566778899aabbccddeeff0011' - - // key = 89abcdeffedcba987654321001234567 - // iv = 48bb57369d2020c66ded73415bfab211 - // plaintext = 445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233 - // ciphertext = 2aac9d5427ddca8f2ef2eaa175012bcb66a1fd8df190a7db9f053f0ee40d79b5 - key = [u8(0x89), 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x01, 0x23, - 0x45, 0x67] - iv = [u8(0x48), 0xbb, 0x57, 0x36, 0x9d, 0x20, 0x20, 0xc6, 0x6d, 0xed, 0x73, 0x41, 0x5b, 0xfa, - 0xb2, 0x11] - orig_iv = iv.clone() - input = [u8(0x44), 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, - 0x00, 0x11, 0x22, 0x33] - output = []u8{len: 32} - mut c4 := sm4.new_cipher(.sm4_encrypt, key)! - c4.crypt_cbc(mut iv, input, mut output)! - assert output.hex() == '2aac9d5427ddca8f2ef2eaa175012bcb66a1fd8df190a7db9f053f0ee40d79b5' - iv = orig_iv.clone() - mut d4 := sm4.new_cipher(.sm4_decrypt, key)! - d4.crypt_cbc(mut iv, output, mut output)! - assert output.hex() == '445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233' +const sm4data_ecb = [ + SM4Data{ + key: '0123456789abcdeffedcba9876543210' + input: '0123456789abcdeffedcba9876543210' + output: '681edf34d206965e86b3e94f536e4246' + }, + SM4Data{ + key: '0123456789abcdeffedcba9876543210' + input: '00112233445566778899aabbccddeeff' + output: '09325c4853832dcb9337a5984f671b9a' + }, + SM4Data{ + key: '456789abcdeffedcba98765432100123' + input: '2233445566778899aabbccddeeff0011' + output: '58ab414d84fb3008b0bee987f97021e6' + }, + SM4Data{ + key: '89abcdeffedcba987654321001234567' + input: '445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233' + output: '5937a929a2d9137216c72a28cd9cf6195937a929a2d9137216c72a28cd9cf619' + }, +] + +const sm4data_cbc = [ + SM4Data{ + key: '0123456789abcdeffedcba9876543210' + iv: '0123456789abcdeffedcba9876543210' + input: '0123456789abcdeffedcba9876543210' + output: '2677f46b09c122cc975533105bd4a22a' + }, + SM4Data{ + key: '0123456789abcdeffedcba9876543210' + iv: '2677f46b09c122cc975533105bd4a22a' + input: '00112233445566778899aabbccddeeff' + output: '2e7b41173b42b90189bbcf4eeb5b5145' + }, + SM4Data{ + key: '0123456789abcdeffedcba9876543210' + iv: '2e7b41173b42b90189bbcf4eeb5b5145' + input: '29860013b6b74c7503c524b62ecd52df' + output: 'cd0cd15a934b3f47476292113d7f35bd' + }, +] + +fn test_sm4_ecb() { + mut key := []u8{} + mut input := []u8{} + mut output := []u8{} + + mut cipher := &sm4.SM4Cipher{} + + for data in sm4data_ecb { + key = '0x${data.key}'.u8_array() + input = '0x${data.input}'.u8_array() + output = [u8(0)].repeat(input.len) // output must be exactly the same length as input + assert key.len == 16 + cipher = sm4.new_cipher(.sm4_encrypt, key)! + cipher.crypt_ecb(input, mut output)! + assert output.hex() == data.output + + cipher = sm4.new_cipher(.sm4_decrypt, key)! + cipher.crypt_ecb(output, mut output)! + assert output.hex() == data.input + } +} + +fn test_sm4_cbc() { + mut key := []u8{} + mut iv := []u8{} + mut input := []u8{} + mut output := []u8{} + mut iv_next := []u8{} + + mut cipher := &sm4.SM4Cipher{} + + // CBC encrypt + for i, data in sm4data_cbc { + key = '0x${data.key}'.u8_array() + iv = '0x${data.iv}'.u8_array() + input = '0x${data.input}'.u8_array() + output = [u8(0)].repeat(input.len) // output must be exactly the same length as input + assert key.len == 16 + assert iv.len == 16 + if i != 0 { + assert iv_next == iv + } + cipher = sm4.new_cipher(.sm4_encrypt, key)! + iv_next = cipher.crypt_cbc(iv, input, mut output)! + dump(iv_next.hex()) + assert output.hex() == data.output + } + // CBC decrypt + for i, data in sm4data_cbc { + key = '0x${data.key}'.u8_array() + iv = '0x${data.iv}'.u8_array() + input = '0x${data.output}'.u8_array() + output = [u8(0)].repeat(input.len) // output must be exactly the same length as input + assert key.len == 16 + assert iv.len == 16 + if i != 0 { + assert iv_next == iv + } + cipher = sm4.new_cipher(.sm4_decrypt, key)! + iv_next = cipher.crypt_cbc(iv, input, mut output)! + dump(iv_next.hex()) + assert output.hex() == data.input + } } fn test_sm4_wrong_length() ! { @@ -187,7 +155,7 @@ fn test_sm4_wrong_length() ! { // wrong iv length test(cbc) fail_flag = false mut c3 := sm4.new_cipher(.sm4_encrypt, [u8(0xff)].repeat(16))! - c3.crypt_cbc(mut [u8(0xff)].repeat(22), [u8(0xff)].repeat(16), mut [u8(0xff)].repeat(16)) or { + c3.crypt_cbc([u8(0xff)].repeat(22), [u8(0xff)].repeat(16), mut [u8(0xff)].repeat(16)) or { fail_flag = true assert err.msg() == 'iv length must be exactly 16 bytes' } @@ -196,7 +164,7 @@ fn test_sm4_wrong_length() ! { // wrong input length test(cbc) fail_flag = false mut c4 := sm4.new_cipher(.sm4_encrypt, [u8(0xff)].repeat(16))! - c4.crypt_cbc(mut [u8(0xff)].repeat(16), [u8(0xff)].repeat(111), mut output) or { + c4.crypt_cbc([u8(0xff)].repeat(16), [u8(0xff)].repeat(111), mut output) or { fail_flag = true assert err.msg() == 'input must be padded to multiple of 16 bytes' } @@ -205,7 +173,7 @@ fn test_sm4_wrong_length() ! { // wrong output length test(cbc) fail_flag = false mut c5 := sm4.new_cipher(.sm4_encrypt, [u8(0xff)].repeat(16))! - c5.crypt_cbc(mut [u8(0xff)].repeat(16), [u8(0xff)].repeat(16), mut output) or { + c5.crypt_cbc([u8(0xff)].repeat(16), [u8(0xff)].repeat(16), mut output) or { fail_flag = true assert err.msg() == 'output must be exactly the same length as input' } -- 2.39.5