diff --git a/CMakeLists.txt b/CMakeLists.txt index cd7be918..3dc13a7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,7 @@ option(ENABLE_SM4_CFB "Enable SM4 CFB mode" ON) option(ENABLE_SM4_CCM "Enable SM4 CCM mode" ON) option(ENABLE_SM4_XTS "Enable SM4 XTS mode" ON) option(ENABLE_SM4_CBC_MAC "Enable SM4-CBC-MAC" ON) +option(ENABLE_SM4_FF1 "Enable SM4 FF1 format-preserving encryption" ON) option(ENABLE_SM9 "Enable SM9" ON) option(ENABLE_CMS "Enable CMS" ON) @@ -463,6 +464,14 @@ if (ENABLE_SM4_XTS) list(APPEND tests sm4_xts) endif() +if (ENABLE_SM4_FF1) + message(STATUS "ENABLE_SM4_FF1 is ON") + add_definitions(-DENABLE_SM4_FF1) + list(APPEND src src/ff1.c) + list(APPEND tools tools/sm4_ff1.c) + list(APPEND tests ff1) +endif() + if (ENABLE_SM9) message(STATUS "ENABLE_SM9 is ON") @@ -825,6 +834,7 @@ add_test(NAME tool_sm4 COMMAND ${CMAKE_COMMAND} -DENABLE_SM4_CCM=${ENABLE_SM4_CCM} -DENABLE_SM4_XTS=${ENABLE_SM4_XTS} -DENABLE_SM4_CBC_MAC=${ENABLE_SM4_CBC_MAC} + -DENABLE_SM4_FF1=${ENABLE_SM4_FF1} -P "${CMAKE_SOURCE_DIR}/cmake/tool_sm4.cmake") if(ENABLE_ZUC) add_test(NAME tool_zuc COMMAND ${CMAKE_COMMAND} -P "${CMAKE_SOURCE_DIR}/cmake/tool_zuc.cmake") @@ -932,7 +942,7 @@ endif() # set(CPACK_PACKAGE_NAME "GmSSL") set(CPACK_PACKAGE_VENDOR "GmSSL develop team") -set(CPACK_PACKAGE_VERSION "3.3.0-dev.1154") +set(CPACK_PACKAGE_VERSION "3.3.0-dev.1155") set(CPACK_PACKAGE_DESCRIPTION_FILE ${PROJECT_SOURCE_DIR}/README.md) set(CPACK_NSIS_MODIFY_PATH ON) include(CPack) diff --git a/cmake/tool_sm4.cmake b/cmake/tool_sm4.cmake index 84e75a93..fb5ae6e4 100644 --- a/cmake/tool_sm4.cmake +++ b/cmake/tool_sm4.cmake @@ -4,6 +4,7 @@ set(SM4_KEY 0123456789abcdeffedcba9876543210) set(SM4_IV 00000000000000000000000000000000) set(SM4_HMAC_KEY 0123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210) set(SM4_XTS_KEY 0123456789abcdeffedcba987654321000112233445566778899aabbccddeeff) +set(SM4_FF1_KEY 2b7e151628aed2a6abf7158809cf4f3c) set(SM4_TEXT "0123456789abcdef0123456789abcdef") function(gmssl_symmetric_roundtrip name) @@ -48,6 +49,12 @@ if(ENABLE_SM4_XTS) -in tool_sm4_xts.cipher -out tool_sm4_xts.decrypt) gmssl_files_equal(tool_sm4_xts.plain tool_sm4_xts.decrypt) endif() +if(ENABLE_SM4_FF1) + gmssl_expect_stdout("2326982895499381" + sm4_ff1 -encrypt -key ${SM4_FF1_KEY} -tweak 39383736353433323130 -digits 6226090102675688) + gmssl_expect_stdout("6226090102675688" + sm4_ff1 -decrypt -key ${SM4_FF1_KEY} -tweak 39383736353433323130 -digits 2326982895499381) +endif() if(ENABLE_SM4_CBC_MAC) gmssl_expect_stdout("9054fccff72871fdad5202c821dbea05\n" sm4_cbc_mac -key ${SM4_KEY} -in_str abc) diff --git a/include/gmssl/ff1.h b/include/gmssl/ff1.h new file mode 100644 index 00000000..d48a9e8e --- /dev/null +++ b/include/gmssl/ff1.h @@ -0,0 +1,41 @@ +/* + * Copyright 2014-2026 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#ifndef GMSSL_FF1_H +#define GMSSL_FF1_H + + +#include +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define FF1_MIN_DIGITS 8 +#define FF1_MAX_DIGITS 18 +#define FF1_MIN_TWEAK_SIZE 0 +#define FF1_MAX_TWEAK_SIZE 11 +#define FF1_NUM_ROUNDS 10 + + +int ff1_init(BLOCK_CIPHER_KEY *key, const BLOCK_CIPHER *cipher, const uint8_t *raw_key); +int ff1_encrypt(const BLOCK_CIPHER_KEY *key, const char *in, size_t inlen, + const uint8_t *tweak, size_t tweaklen, char *out); +int ff1_decrypt(const BLOCK_CIPHER_KEY *key, const char *in, size_t inlen, + const uint8_t *tweak, size_t tweaklen, char *out); + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/gmssl/socket.h b/include/gmssl/socket.h index 5a766c37..d9f87e51 100644 --- a/include/gmssl/socket.h +++ b/include/gmssl/socket.h @@ -75,6 +75,7 @@ int tls_socket_set_nonblocking(tls_socket_t sock, int nonblock); tls_socket_t tls_socket_invalid(void); int tls_socket_is_valid(tls_socket_t sock); int tls_socket_create(tls_socket_t *sock, int af, int type, int protocl); +int tls_socket_get_addr(const char *host, int port, struct sockaddr_in *addr); int tls_socket_connect(tls_socket_t sock, const struct sockaddr_in *addr); int tls_socket_bind(tls_socket_t sock, const struct sockaddr_in *addr); int tls_socket_listen(tls_socket_t sock, int backlog); diff --git a/include/gmssl/version.h b/include/gmssl/version.h index fe187caa..7ae21e45 100644 --- a/include/gmssl/version.h +++ b/include/gmssl/version.h @@ -18,7 +18,7 @@ extern "C" { #define GMSSL_VERSION_NUM 30300 -#define GMSSL_VERSION_STR "GmSSL 3.3.0-dev.1154" +#define GMSSL_VERSION_STR "GmSSL 3.3.0-dev.1155" int gmssl_version_num(void); const char *gmssl_version_str(void); diff --git a/src/ff1.c b/src/ff1.c new file mode 100644 index 00000000..4d573332 --- /dev/null +++ b/src/ff1.c @@ -0,0 +1,308 @@ +/* + * Copyright 2014-2026 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include +#include +#include +#include + + +static const uint32_t ff1_radix10_mod[] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, +}; + +static const size_t ff1_radix10_b[] = { + 0, 1, 1, 2, 2, 3, 3, 3, 4, 4, +}; + +int ff1_init(BLOCK_CIPHER_KEY *key, const BLOCK_CIPHER *cipher, const uint8_t *raw_key) +{ + if (!key || !cipher || !raw_key) { + error_print(); + return -1; + } + if (cipher->block_size != BLOCK_CIPHER_BLOCK_SIZE) { + error_print(); + return -1; + } + if (block_cipher_set_encrypt_key(key, cipher, raw_key) != 1) { + error_print(); + return -1; + } + return 1; +} + +static int ff1_digits_to_num(const char *digits, size_t ndigits, uint32_t *num) +{ + uint32_t value = 0; + size_t i; + + if (!digits || !num || ndigits > FF1_MAX_DIGITS/2) { + error_print(); + return -1; + } + for (i = 0; i < ndigits; i++) { + if (digits[i] < '0' || digits[i] > '9') { + error_print(); + return -1; + } + value = value * 10 + (uint32_t)(digits[i] - '0'); + } + *num = value; + return 1; +} + +static int ff1_num_to_digits(uint32_t num, size_t ndigits, char *digits) +{ + if (!digits || ndigits > FF1_MAX_DIGITS/2 || num >= ff1_radix10_mod[ndigits]) { + error_print(); + return -1; + } + while (ndigits) { + digits[--ndigits] = (char)('0' + num % 10); + num /= 10; + } + return 1; +} + +static int ff1_check_args(const BLOCK_CIPHER_KEY *key, const char *in, size_t inlen, + const uint8_t *tweak, size_t tweaklen, char *out) +{ + size_t i; + + if (!key || !key->cipher || !in || !out || (!tweak && tweaklen)) { + error_print(); + return -1; + } + if (key->cipher->block_size != BLOCK_CIPHER_BLOCK_SIZE) { + error_print(); + return -1; + } + if (inlen < FF1_MIN_DIGITS || inlen > FF1_MAX_DIGITS) { + error_print(); + return -1; + } + if (tweaklen < FF1_MIN_TWEAK_SIZE || tweaklen > FF1_MAX_TWEAK_SIZE) { + error_print(); + return -1; + } + for (i = 0; i < inlen; i++) { + if (in[i] < '0' || in[i] > '9') { + error_print(); + return -1; + } + } + return 1; +} + +static int ff1_init_pblock(const BLOCK_CIPHER_KEY *key, uint8_t pblock[16], + size_t u, size_t n, size_t tweaklen) +{ + static const uint8_t ff1_radix10_pblock[16] = { + 0x01, 0x02, 0x01, 0x00, 0x00, 0x0a, 0x0a, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + }; + + memcpy(pblock, ff1_radix10_pblock, 16); + pblock[7] = (uint8_t)u; + PUTU32(pblock + 8, (uint32_t)n); + PUTU32(pblock + 12, (uint32_t)tweaklen); + + if (block_cipher_encrypt(key, pblock, pblock) != 1) { + error_print(); + return -1; + } + return 1; +} + +static int ff1_round(const BLOCK_CIPHER_KEY *key, const uint8_t pblock[16], + const uint8_t *tweak, size_t tweaklen, size_t bsize, int round, uint32_t num, uint64_t *y) +{ + uint8_t qblock[32] = {0}; + uint8_t block[16]; + size_t padlen; + size_t offset; + size_t qlen; + size_t i; + + if (!key || !pblock || (!tweak && tweaklen) || !bsize || bsize > sizeof(uint32_t) + || !y || round < 0 || round > 0xff) { + error_print(); + return -1; + } + + /* Keep a full zero padding block when tweak || round || NUM(B) is block-aligned. */ + padlen = 16 - (tweaklen + 1 + bsize) % 16; + qlen = tweaklen + padlen + 1 + bsize; + if (!qlen || qlen > sizeof(qblock) || qlen % 16) { + error_print(); + return -1; + } + if (tweaklen) { + memcpy(qblock, tweak, tweaklen); + } + offset = tweaklen + padlen; + qblock[offset++] = (uint8_t)round; + for (i = 0; i < bsize; i++) { + qblock[offset + bsize - 1 - i] = (uint8_t)(num >> (8 * i)); + } + + for (i = 0; i < sizeof(block); i++) { + block[i] = pblock[i] ^ qblock[i]; + } + if (block_cipher_encrypt(key, block, block) != 1) { + error_print(); + return -1; + } + for (offset = 16; offset < qlen; offset += 16) { + for (i = 0; i < sizeof(block); i++) { + block[i] ^= qblock[offset + i]; + } + if (block_cipher_encrypt(key, block, block) != 1) { + error_print(); + return -1; + } + } + + *y = GETU64(block); + return 1; +} + +int ff1_encrypt(const BLOCK_CIPHER_KEY *key, const char *in, size_t inlen, + const uint8_t *tweak, size_t tweaklen, char *out) +{ + size_t u; + size_t v; + uint32_t a; + uint32_t b; + size_t alen; + size_t blen; + uint64_t y; + uint32_t ymod; + uint32_t c; + uint8_t pblock[16]; + size_t bsize; + int i; + + if (ff1_check_args(key, in, inlen, tweak, tweaklen, out) != 1) { + error_print(); + return -1; + } + + u = inlen / 2; + v = inlen - u; + + if (ff1_digits_to_num(in, u, &a) != 1 + || ff1_digits_to_num(in + u, v, &b) != 1 + || ff1_init_pblock(key, pblock, u, inlen, tweaklen) != 1) { + error_print(); + return -1; + } + alen = u; + blen = v; + bsize = ff1_radix10_b[v]; + + for (i = 0; i < FF1_NUM_ROUNDS; i++) { + size_t m = (i & 1) ? v : u; + + if (ff1_round(key, pblock, tweak, tweaklen, bsize, i, b, &y) != 1) { + error_print(); + return -1; + } + ymod = (uint32_t)(y % ff1_radix10_mod[m]); + c = (a + ymod) % ff1_radix10_mod[m]; + a = b; + alen = blen; + b = c; + blen = m; + } + + if (alen != u || blen != v) { + error_print(); + return -1; + } + if (ff1_num_to_digits(a, alen, out) != 1 + || ff1_num_to_digits(b, blen, out + alen) != 1) { + error_print(); + return -1; + } + return 1; +} + +int ff1_decrypt(const BLOCK_CIPHER_KEY *key, const char *in, size_t inlen, + const uint8_t *tweak, size_t tweaklen, char *out) +{ + size_t u; + size_t v; + uint32_t a; + uint32_t b; + size_t alen; + size_t blen; + uint64_t y; + uint32_t ymod; + uint32_t c; + uint8_t pblock[16]; + size_t bsize; + int i; + + if (ff1_check_args(key, in, inlen, tweak, tweaklen, out) != 1) { + error_print(); + return -1; + } + + u = inlen / 2; + v = inlen - u; + + if (ff1_digits_to_num(in, u, &a) != 1 + || ff1_digits_to_num(in + u, v, &b) != 1 + || ff1_init_pblock(key, pblock, u, inlen, tweaklen) != 1) { + error_print(); + return -1; + } + alen = u; + blen = v; + bsize = ff1_radix10_b[v]; + + for (i = FF1_NUM_ROUNDS - 1; i >= 0; i--) { + size_t m = (i & 1) ? v : u; + + c = b; + b = a; + blen = alen; + + if (ff1_round(key, pblock, tweak, tweaklen, bsize, i, b, &y) != 1) { + error_print(); + return -1; + } + ymod = (uint32_t)(y % ff1_radix10_mod[m]); + a = c; + a = (a >= ymod) ? a - ymod : a + ff1_radix10_mod[m] - ymod; + alen = m; + } + + if (alen != u || blen != v) { + error_print(); + return -1; + } + if (ff1_num_to_digits(a, alen, out) != 1 + || ff1_num_to_digits(b, blen, out + alen) != 1) { + error_print(); + return -1; + } + return 1; +} diff --git a/src/http.c b/src/http.c index a20bd52b..ef0acfa0 100644 --- a/src/http.c +++ b/src/http.c @@ -120,7 +120,6 @@ int http_get(const char *uri, uint8_t *buf, size_t *contentlen, size_t buflen) char host[128]; int port; char path[256]; - struct hostent *hp; struct sockaddr_in server; tls_socket_t sock = tls_socket_invalid(); char get[sizeof(HTTP_GET_TEMPLATE) + sizeof(host) + sizeof(path)]; @@ -141,13 +140,10 @@ int http_get(const char *uri, uint8_t *buf, size_t *contentlen, size_t buflen) } // connect and send request - if (!(hp = gethostbyname(host))) { + if (tls_socket_get_addr(host, port, &server) != 1) { error_print(); return -1; } - server.sin_addr = *((struct in_addr *)hp->h_addr_list[0]); - server.sin_family = AF_INET; - server.sin_port = htons(port); if (tls_socket_create(&sock, AF_INET, SOCK_STREAM, 0) != 1) { error_print(); @@ -198,7 +194,6 @@ int http_post(const char *uri, const char *content_type, char host[128]; int port; char path[256]; - struct hostent *hp; struct sockaddr_in server; tls_socket_t sock = tls_socket_invalid(); char post[1024]; @@ -223,13 +218,10 @@ int http_post(const char *uri, const char *content_type, return -1; } - if (!(hp = gethostbyname(host))) { + if (tls_socket_get_addr(host, port, &server) != 1) { error_print(); return -1; } - server.sin_addr = *((struct in_addr *)hp->h_addr_list[0]); - server.sin_family = AF_INET; - server.sin_port = htons(port); if (tls_socket_create(&sock, AF_INET, SOCK_STREAM, 0) != 1) { error_print(); diff --git a/src/http_win.c b/src/http_win.c index ae3147fa..c6f88279 100644 --- a/src/http_win.c +++ b/src/http_win.c @@ -120,7 +120,6 @@ int http_get(const char *uri, uint8_t *buf, size_t *contentlen, size_t buflen) char host[128]; int port; char path[256]; - struct hostent *hp; struct sockaddr_in server; tls_socket_t sock = tls_socket_invalid(); char get[sizeof(HTTP_GET_TEMPLATE) + sizeof(host) + sizeof(path)]; @@ -148,13 +147,10 @@ int http_get(const char *uri, uint8_t *buf, size_t *contentlen, size_t buflen) socket_lib_inited = 1; // connect and send request - if (!(hp = gethostbyname(host))) { + if (tls_socket_get_addr(host, port, &server) != 1) { error_print(); goto end; } - server.sin_addr = *((struct in_addr *)hp->h_addr_list[0]); - server.sin_family = AF_INET; - server.sin_port = htons(port); if (tls_socket_create(&sock, AF_INET, SOCK_STREAM, 0) != 1) { error_print(); @@ -207,7 +203,6 @@ int http_post(const char *uri, const char *content_type, char host[128]; int port; char path[256]; - struct hostent *hp; struct sockaddr_in server; tls_socket_t sock = tls_socket_invalid(); char post[1024]; @@ -239,13 +234,10 @@ int http_post(const char *uri, const char *content_type, } socket_lib_inited = 1; - if (!(hp = gethostbyname(host))) { + if (tls_socket_get_addr(host, port, &server) != 1) { error_print(); goto end; } - server.sin_addr = *((struct in_addr *)hp->h_addr_list[0]); - server.sin_family = AF_INET; - server.sin_port = htons(port); if (tls_socket_create(&sock, AF_INET, SOCK_STREAM, 0) != 1) { error_print(); diff --git a/src/socket.c b/src/socket.c index 4f582ddc..a69016af 100644 --- a/src/socket.c +++ b/src/socket.c @@ -18,6 +18,7 @@ #ifdef WIN32 #include +#include #endif @@ -29,6 +30,47 @@ static int tls_socket_should_print_error(int err, int is_read) && type != TLS_SOCKET_ERR_INTERRUPTED; } +int tls_socket_get_addr(const char *host, int port, struct sockaddr_in *addr) +{ + char service[16]; + struct addrinfo hints; + struct addrinfo *res = NULL; + int err; + + if (!host || !addr || port <= 0 || port > 65535) { + error_print(); + return -1; + } + if (snprintf(service, sizeof(service), "%d", port) <= 0) { + error_print(); + return -1; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + if ((err = getaddrinfo(host, service, &hints, &res)) != 0) { +#ifdef WIN32 + error_print_msg("getaddrinfo error: %d (%s)\n", err, gai_strerrorA(err)); +#else + error_print_msg("getaddrinfo error: %d (%s)\n", err, gai_strerror(err)); +#endif + return -1; + } + if (!res || res->ai_addrlen < sizeof(struct sockaddr_in)) { + error_print(); + if (res) { + freeaddrinfo(res); + } + return -1; + } + + memcpy(addr, res->ai_addr, sizeof(struct sockaddr_in)); + freeaddrinfo(res); + return 1; +} + #ifdef WIN32 int tls_socket_get_error(void) diff --git a/tests/ff1test.c b/tests/ff1test.c new file mode 100644 index 00000000..52637bb3 --- /dev/null +++ b/tests/ff1test.c @@ -0,0 +1,273 @@ +/* + * Copyright 2014-2026 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include +#include +#include +#include + + +typedef struct { + const uint8_t key[16]; + const char *plaintext; + const uint8_t *tweak; + size_t tweaklen; + const char *ciphertext; +} FF1_TEST; + +static const uint8_t ff1_sm4_tweak1[] = { + 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, + 0x31, 0x30, +}; + +static const uint8_t ff1_sm4_tweak2[] = { + 0x37, 0x38, 0x39, 0x36, 0x70, 0x71, 0x72, 0x73, + 0x74, 0x75, 0x76, +}; + +static const FF1_TEST ff1_sm4_tests[] = { + { + { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, + }, + "6226090102675688", + ff1_sm4_tweak1, + sizeof(ff1_sm4_tweak1), + "2326982895499381", + }, + { + { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, + }, + "110107197203192876", + ff1_sm4_tweak2, + sizeof(ff1_sm4_tweak2), + "755842115213533405", + }, + { + { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, + }, + "13687260594", + NULL, + 0, + "37914960556", + }, +}; + +#ifdef ENABLE_AES +static const uint8_t ff1_aes128_tweak1[] = { + 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, + 0x31, 0x30, +}; + +static const uint8_t ff1_aes128_tweak4[] = { + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, +}; + +static const FF1_TEST ff1_aes128_tests[] = { + { + { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, + }, + "0123456789", + ff1_aes128_tweak1, + sizeof(ff1_aes128_tweak1), + "6124200773", + }, + { + { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, + }, + "0123456789", + NULL, + 0, + "2433477484", + }, + { + { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, + }, + "999999999", + ff1_aes128_tweak4, + sizeof(ff1_aes128_tweak4), + "658229573", + }, +}; +#endif + +static int test_ff1_sm4(void) +{ + const char *plaintext = "99999999999999999"; + size_t plaintext_len = strlen(plaintext); + const char sentinel = '#'; + const uint8_t key[16] = {0}; + const uint8_t tweak[8] = { + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + }; + BLOCK_CIPHER_KEY block_key; + char ciphertext[FF1_MAX_DIGITS + 1]; + char decrypted[FF1_MAX_DIGITS + 1]; + + if (ff1_init(&block_key, BLOCK_CIPHER_sm4(), key) != 1) { + error_print(); + return -1; + } + ciphertext[plaintext_len] = sentinel; + if (ff1_encrypt(&block_key, plaintext, plaintext_len, + tweak, sizeof(tweak), ciphertext) != 1) { + error_print(); + return -1; + } + if (ciphertext[plaintext_len] != sentinel) { + error_print(); + return -1; + } + ciphertext[plaintext_len] = '\0'; + decrypted[plaintext_len] = sentinel; + if (ff1_decrypt(&block_key, ciphertext, plaintext_len, + tweak, sizeof(tweak), decrypted) != 1) { + error_print(); + return -1; + } + if (decrypted[plaintext_len] != sentinel) { + error_print(); + return -1; + } + decrypted[plaintext_len] = '\0'; + if (strcmp(plaintext, decrypted) != 0) { + error_print(); + return -1; + } + + printf("ff1-sm4-encrypt(\"%s\") = \"%s\"\n", plaintext, ciphertext); + printf("%s() ok\n", __FUNCTION__); + return 1; +} + +static int test_ff1_sm4_vectors(void) +{ + BLOCK_CIPHER_KEY block_key; + char ciphertext[FF1_MAX_DIGITS + 1]; + char decrypted[FF1_MAX_DIGITS + 1]; + size_t i; + int err = 0; + + for (i = 0; i < sizeof(ff1_sm4_tests)/sizeof(ff1_sm4_tests[0]); i++) { + const FF1_TEST *test = &ff1_sm4_tests[i]; + + if (ff1_init(&block_key, BLOCK_CIPHER_sm4(), test->key) != 1) { + error_print(); + return -1; + } + if (ff1_encrypt(&block_key, test->plaintext, strlen(test->plaintext), + test->tweak, test->tweaklen, ciphertext) != 1) { + error_print(); + return -1; + } + ciphertext[strlen(test->plaintext)] = '\0'; + if (strcmp(ciphertext, test->ciphertext) != 0) { + fprintf(stderr, "test %zu: got %s, expected %s\n", + i + 1, ciphertext, test->ciphertext); + error_print(); + err++; + continue; + } + if (ff1_decrypt(&block_key, test->ciphertext, strlen(test->ciphertext), + test->tweak, test->tweaklen, decrypted) != 1) { + error_print(); + return -1; + } + decrypted[strlen(test->ciphertext)] = '\0'; + if (strcmp(decrypted, test->plaintext) != 0) { + error_print(); + return -1; + } + } + if (err) { + return -1; + } + + printf("%s() ok\n", __FUNCTION__); + return 1; +} + +#ifdef ENABLE_AES +static int test_ff1_aes128_vectors(void) +{ + BLOCK_CIPHER_KEY block_key; + char ciphertext[FF1_MAX_DIGITS + 1]; + char decrypted[FF1_MAX_DIGITS + 1]; + size_t i; + int err = 0; + + for (i = 0; i < sizeof(ff1_aes128_tests)/sizeof(ff1_aes128_tests[0]); i++) { + const FF1_TEST *test = &ff1_aes128_tests[i]; + + if (ff1_init(&block_key, BLOCK_CIPHER_aes128(), test->key) != 1) { + error_print(); + return -1; + } + if (ff1_encrypt(&block_key, test->plaintext, strlen(test->plaintext), + test->tweak, test->tweaklen, ciphertext) != 1) { + error_print(); + return -1; + } + ciphertext[strlen(test->plaintext)] = '\0'; + if (strcmp(ciphertext, test->ciphertext) != 0) { + fprintf(stderr, "AES-128 test %zu: got %s, expected %s\n", + i + 1, ciphertext, test->ciphertext); + error_print(); + err++; + continue; + } + if (ff1_decrypt(&block_key, test->ciphertext, strlen(test->ciphertext), + test->tweak, test->tweaklen, decrypted) != 1) { + error_print(); + return -1; + } + decrypted[strlen(test->ciphertext)] = '\0'; + if (strcmp(decrypted, test->plaintext) != 0) { + error_print(); + return -1; + } + } + if (err) { + return -1; + } + + printf("%s() ok\n", __FUNCTION__); + return 1; +} +#endif + +int main(void) +{ + int err = 0; + + if (test_ff1_sm4() != 1) { + err++; + } + if (test_ff1_sm4_vectors() != 1) { + err++; + } +#ifdef ENABLE_AES + if (test_ff1_aes128_vectors() != 1) { + err++; + } +#endif + + return err; +} diff --git a/tools/gmssl.c b/tools/gmssl.c index 37201abc..e20280dc 100644 --- a/tools/gmssl.c +++ b/tools/gmssl.c @@ -56,6 +56,9 @@ extern int sm4_gcm_main(int argc, char **argv); #ifdef ENABLE_SM4_XTS extern int sm4_xts_main(int argc, char **argv); #endif +#ifdef ENABLE_SM4_FF1 +extern int sm4_ff1_main(int argc, char **argv); +#endif extern int sm4_cbc_sm3_hmac_main(int argc, char **argv); extern int sm4_ctr_sm3_hmac_main(int argc, char **argv); #ifdef ENABLE_SM4_CBC_MAC @@ -160,6 +163,9 @@ static const char *options = #ifdef ENABLE_SM4_XTS " sm4_xts Encrypt or decrypt with SM4 XTS\n" #endif +#ifdef ENABLE_SM4_FF1 + " sm4_ff1 Encrypt or decrypt digits with SM4 FF1\n" +#endif #ifdef ENABLE_SM4_ECB " sm4_ecb Encrypt or decrypt with SM4 ECB\n" #endif @@ -355,6 +361,10 @@ int main(int argc, char **argv) #if ENABLE_SM4_XTS } else if (!strcmp(*argv, "sm4_xts")) { return sm4_xts_main(argc, argv); +#endif +#if ENABLE_SM4_FF1 + } else if (!strcmp(*argv, "sm4_ff1")) { + return sm4_ff1_main(argc, argv); #endif } else if (!strcmp(*argv, "sm4_cbc_sm3_hmac")) { return sm4_cbc_sm3_hmac_main(argc, argv); diff --git a/tools/sm4_ff1.c b/tools/sm4_ff1.c new file mode 100644 index 00000000..b05fe53e --- /dev/null +++ b/tools/sm4_ff1.c @@ -0,0 +1,356 @@ +/* + * Copyright 2014-2026 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define SM4_FF1_MAX_TWEAK_SIZE FF1_MAX_TWEAK_SIZE +#define ID_CARD_DIGITS 18 +#define ID_CARD_BODY_DIGITS 17 + + +static const char *usage = + "{-encrypt|-decrypt} -key hex [-tweak hex] [-idcard|-bankcard] [-digits digits]"; + +static const char *options = +"\n" +"Options\n" +"\n" +" -encrypt Encrypt\n" +" -decrypt Decrypt\n" +" -key hex SM4 key in HEX format\n" +" -tweak hex FF1 tweak in HEX format\n" +" -digits digits Input digits, default from stdin\n" +" -idcard Input is a Chinese resident identity card number\n" +" -bankcard Input is a bank card number with Luhn check digit\n" +"\n"; + +static uint8_t *read_content(FILE *infp, size_t *outlen, const char *prog) +{ + const size_t maxlen = 4096; + uint8_t *buf = NULL; + size_t len; + + if (!(buf = malloc(maxlen + 1))) { + fprintf(stderr, "gmssl %s: malloc failure\n", prog); + return NULL; + } + len = fread(buf, 1, maxlen, infp); + if (ferror(infp)) { + fprintf(stderr, "gmssl %s: read failure : %s\n", prog, strerror(errno)); + free(buf); + return NULL; + } + if (!feof(infp)) { + fprintf(stderr, "gmssl %s: input too long\n", prog); + free(buf); + return NULL; + } + while (len && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) { + len--; + } + buf[len] = 0; + *outlen = len; + return buf; +} + +static int is_digits(const char *s, size_t len) +{ + size_t i; + + if (!s) { + return 0; + } + for (i = 0; i < len; i++) { + if (s[i] < '0' || s[i] > '9') { + return 0; + } + } + return 1; +} + +static int idcard_check_digit(const char body[ID_CARD_BODY_DIGITS]) +{ + static const int weights[ID_CARD_BODY_DIGITS] = { + 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, + }; + static const char check_digits[] = "10X98765432"; + int sum = 0; + size_t i; + + if (!is_digits(body, ID_CARD_BODY_DIGITS)) { + return -1; + } + for (i = 0; i < ID_CARD_BODY_DIGITS; i++) { + sum += (body[i] - '0') * weights[i]; + } + return check_digits[sum % 11]; +} + +static int idcard_check(const char *s, size_t len) +{ + int ch; + char last; + + if (!s || len != ID_CARD_DIGITS) { + return -1; + } + ch = idcard_check_digit(s); + if (ch < 0) { + return -1; + } + last = s[ID_CARD_DIGITS - 1]; + if (last == 'x') { + last = 'X'; + } + return last == ch ? 1 : -1; +} + +static int luhn_check_digit(const char *body, size_t len) +{ + int sum = 0; + int double_digit = 1; + + if (!is_digits(body, len)) { + return -1; + } + while (len) { + int digit = body[--len] - '0'; + if (double_digit) { + digit *= 2; + if (digit > 9) { + digit -= 9; + } + } + sum += digit; + double_digit = !double_digit; + } + return '0' + (10 - sum % 10) % 10; +} + +static int bankcard_check(const char *s, size_t len) +{ + int ch; + + if (!s || len < FF1_MIN_DIGITS + 1 || len > FF1_MAX_DIGITS + 1 || !is_digits(s, len)) { + return -1; + } + ch = luhn_check_digit(s, len - 1); + if (ch < 0) { + return -1; + } + return s[len - 1] == ch ? 1 : -1; +} + +int sm4_ff1_main(int argc, char **argv) +{ + int ret = 1; + char *prog = argv[0]; + int enc = -1; + int idcard = 0; + int bankcard = 0; + char *keyhex = NULL; + char *tweakhex = NULL; + char *digits = NULL; + uint8_t key[16]; + uint8_t tweak[SM4_FF1_MAX_TWEAK_SIZE]; + size_t keylen = 0; + size_t tweaklen = 0; + uint8_t *inbuf = NULL; + int inbuf_alloc = 0; + size_t inlen; + char outbuf[FF1_MAX_DIGITS + 2]; + size_t bodylen; + BLOCK_CIPHER_KEY block_key; + + argc--; + argv++; + + if (argc < 1) { + fprintf(stderr, "usage: gmssl %s %s\n", prog, usage); + return 1; + } + + while (argc > 0) { + if (!strcmp(*argv, "-help")) { + printf("usage: gmssl %s %s\n", prog, usage); + printf("%s\n", options); + ret = 0; + goto end; + } else if (!strcmp(*argv, "-encrypt")) { + if (enc == 0) { + fprintf(stderr, "gmssl %s: `-encrypt` and `-decrypt` should not be used together\n", prog); + goto end; + } + enc = 1; + } else if (!strcmp(*argv, "-decrypt")) { + if (enc == 1) { + fprintf(stderr, "gmssl %s: `-encrypt` and `-decrypt` should not be used together\n", prog); + goto end; + } + enc = 0; + } else if (!strcmp(*argv, "-key")) { + if (--argc < 1) goto bad; + keyhex = *(++argv); + if (strlen(keyhex) != sizeof(key) * 2) { + fprintf(stderr, "gmssl %s: invalid key length\n", prog); + goto end; + } + if (hex_to_bytes(keyhex, strlen(keyhex), key, &keylen) != 1 || keylen != sizeof(key)) { + fprintf(stderr, "gmssl %s: invalid key hex digits\n", prog); + goto end; + } + } else if (!strcmp(*argv, "-tweak")) { + if (--argc < 1) goto bad; + tweakhex = *(++argv); + if (strlen(tweakhex) > sizeof(tweak) * 2) { + fprintf(stderr, "gmssl %s: invalid tweak length\n", prog); + goto end; + } + if (!strlen(tweakhex)) { + tweaklen = 0; + } else if (hex_to_bytes(tweakhex, strlen(tweakhex), tweak, &tweaklen) != 1) { + fprintf(stderr, "gmssl %s: invalid tweak hex digits\n", prog); + goto end; + } + } else if (!strcmp(*argv, "-digits")) { + if (--argc < 1) goto bad; + if (digits) { + fprintf(stderr, "gmssl %s: option `-digits` should be set only once\n", prog); + goto end; + } + digits = *(++argv); + } else if (!strcmp(*argv, "-idcard")) { + if (bankcard) { + fprintf(stderr, "gmssl %s: `-idcard` and `-bankcard` should not be used together\n", prog); + goto end; + } + idcard = 1; + } else if (!strcmp(*argv, "-bankcard")) { + if (idcard) { + fprintf(stderr, "gmssl %s: `-idcard` and `-bankcard` should not be used together\n", prog); + goto end; + } + bankcard = 1; + } else { + fprintf(stderr, "gmssl %s: illegal option `%s`\n", prog, *argv); + goto end; +bad: + fprintf(stderr, "gmssl %s: `%s` option value missing\n", prog, *argv); + goto end; + } + + argc--; + argv++; + } + + if (enc < 0) { + fprintf(stderr, "gmssl %s: option -encrypt or -decrypt should be set\n", prog); + goto end; + } + if (!keyhex) { + fprintf(stderr, "gmssl %s: option `-key` missing\n", prog); + goto end; + } + if (tweakhex && tweaklen > FF1_MAX_TWEAK_SIZE) { + fprintf(stderr, "gmssl %s: invalid tweak length\n", prog); + goto end; + } + if (digits) { + inbuf = (uint8_t *)digits; + inlen = strlen(digits); + } else { + if (!(inbuf = read_content(stdin, &inlen, prog))) { + goto end; + } + inbuf_alloc = 1; + } + if (!inlen) { + fprintf(stderr, "gmssl %s: empty input\n", prog); + goto end; + } + + if (idcard) { + if (idcard_check((char *)inbuf, inlen) != 1) { + fprintf(stderr, "gmssl %s: invalid identity card number\n", prog); + goto end; + } + bodylen = ID_CARD_BODY_DIGITS; + } else if (bankcard) { + if (bankcard_check((char *)inbuf, inlen) != 1) { + fprintf(stderr, "gmssl %s: invalid bank card number\n", prog); + goto end; + } + bodylen = inlen - 1; + } else { + if (inlen < FF1_MIN_DIGITS || inlen > FF1_MAX_DIGITS || !is_digits((char *)inbuf, inlen)) { + fprintf(stderr, "gmssl %s: invalid input digits\n", prog); + goto end; + } + bodylen = inlen; + } + + if (ff1_init(&block_key, BLOCK_CIPHER_sm4(), key) != 1) { + error_print(); + goto end; + } + if (enc) { + if (ff1_encrypt(&block_key, (char *)inbuf, bodylen, tweak, tweaklen, outbuf) != 1) { + error_print(); + goto end; + } + } else { + if (ff1_decrypt(&block_key, (char *)inbuf, bodylen, tweak, tweaklen, outbuf) != 1) { + error_print(); + goto end; + } + } + + if (idcard) { + int ch = idcard_check_digit(outbuf); + if (ch < 0) { + error_print(); + goto end; + } + outbuf[bodylen] = (char)ch; + outbuf[bodylen + 1] = '\0'; + bodylen++; + } else if (bankcard) { + int ch = luhn_check_digit(outbuf, bodylen); + if (ch < 0) { + error_print(); + goto end; + } + outbuf[bodylen] = (char)ch; + outbuf[bodylen + 1] = '\0'; + bodylen++; + } + + if (fwrite(outbuf, 1, bodylen, stdout) != bodylen) { + fprintf(stderr, "gmssl %s: output failure : %s\n", prog, strerror(errno)); + goto end; + } + + ret = 0; + +end: + if (inbuf_alloc) free(inbuf); + gmssl_secure_clear(key, sizeof(key)); + gmssl_secure_clear(tweak, sizeof(tweak)); + gmssl_secure_clear(&block_key, sizeof(block_key)); + return ret; +} diff --git a/tools/tlcp_client.c b/tools/tlcp_client.c index ab40f59b..2c638cdf 100644 --- a/tools/tlcp_client.c +++ b/tools/tlcp_client.c @@ -228,7 +228,6 @@ int tlcp_client_main(int argc, char *argv[]) char *infile = NULL; char *certoutfile = NULL; int verbose = 0; - struct hostent *hp; struct sockaddr_in server; tls_socket_t sock = tls_socket_invalid(); TLS_CTX ctx; @@ -479,15 +478,11 @@ bad: goto end; } - if (!(hp = gethostbyname(host))) { + if (tls_socket_get_addr(host, port, &server) != 1) { fprintf(stderr, "%s: failed to parse host name '%s'\n", prog, host); goto end; } - server.sin_addr = *((struct in_addr *)hp->h_addr_list[0]); - server.sin_family = AF_INET; - server.sin_port = htons(port); - if (tls_socket_connect(sock, &server) != 1) { fprintf(stderr, "%s: failed to connect socket\n", prog); goto end; diff --git a/tools/tls12_client.c b/tools/tls12_client.c index b29bf03e..6dd383e5 100644 --- a/tools/tls12_client.c +++ b/tools/tls12_client.c @@ -220,7 +220,6 @@ int tls12_client_main(int argc, char *argv[]) int verbose = 0; TLS_CTX ctx; TLS_CONNECT conn; - struct hostent *hp; struct sockaddr_in server; tls_socket_t sock = tls_socket_invalid(); char buf[1024] = {0}; @@ -469,15 +468,11 @@ bad: goto end; } - if (!(hp = gethostbyname(host))) { + if (tls_socket_get_addr(host, port, &server) != 1) { fprintf(stderr, "%s: failed to parse host name '%s'\n", prog, host); goto end; } - server.sin_addr = *((struct in_addr *)hp->h_addr_list[0]); - server.sin_family = AF_INET; - server.sin_port = htons(port); - if (tls_socket_connect(sock, &server) != 1) { fprintf(stderr, "%s: socket connect error\n", prog); goto end; diff --git a/tools/tls13_client.c b/tools/tls13_client.c index 6871ccca..4ee92c6d 100644 --- a/tools/tls13_client.c +++ b/tools/tls13_client.c @@ -217,7 +217,6 @@ int tls13_client_main(int argc, char *argv[]) TLS_CTX ctx; TLS_CONNECT conn; - struct hostent *hp; struct sockaddr_in server; tls_socket_t sock = tls_socket_invalid(); char buf[1024] = {0}; @@ -782,17 +781,10 @@ bad: goto end; } - if (!(hp = gethostbyname(host))) { -#ifdef WIN32 - fprintf(stderr, "%s: parse -host value error: %d\n", prog, WSAGetLastError()); -#else - fprintf(stderr, "%s: parse -host value error: %s\n", prog, hstrerror(h_errno)); -#endif + if (tls_socket_get_addr(host, port, &server) != 1) { + fprintf(stderr, "%s: parse -host value error\n", prog); goto end; } - server.sin_addr = *((struct in_addr *)hp->h_addr_list[0]); - server.sin_family = AF_INET; - server.sin_port = htons(port); if (tls_socket_create(&sock, AF_INET, SOCK_STREAM, 0) != 1) { fprintf(stderr, "%s: socket create error\n", prog);