news 2026/6/23 21:54:12

C语言实战:基于OpenSSL的RSA加密与数字签名完整实现指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言实战:基于OpenSSL的RSA加密与数字签名完整实现指南

1. 项目概述:为什么需要亲手实现RSA流程?

在信息安全领域,加密和签名是两块基石。你可能听说过HTTPS、SSH,或者遇到过软件更新时的签名验证,这些场景的背后,RSA算法扮演着核心角色。作为一个C语言开发者,如果仅仅停留在调用某个库的“黑盒”函数,而不去理解其内部的数据流转和关键步骤,就如同开车只懂踩油门,不懂保养和故障排查。当遇到“RSA public key not find”或“无法验证数字签名”这类错误时,往往会束手无策。

这个项目的目的,就是带你用C语言和OpenSSL库,从零开始,完整地走一遍RSA的非对称加密和数字签名流程。这不是一个简单的API调用演示,而是一次深入的“解剖”实验。我们将亲手生成密钥对、组织待处理的数据、调用加解密函数,并最终验证整个流程的闭环。通过这个过程,你不仅能掌握OpenSSL中RSA相关函数的使用方法,更能透彻理解“公钥加密、私钥解密”和“私钥签名、公钥验签”这两大核心机制背后的数据逻辑,为日后处理更复杂的密码学应用、调试安全通信问题打下坚实基础。

2. 核心原理与OpenSSL对象模型解析

2.1 RSA算法核心思想简述

RSA的安全性建立在大数分解的困难性上。简单来说,它涉及几个关键步骤:

  1. 密钥生成:随机选择两个大质数p和q,计算它们的乘积n(模数)。再计算欧拉函数φ(n) = (p-1)*(q-1)。选择一个与φ(n)互质的整数e作为公钥指数,通常为65537。接着,计算e关于φ(n)的模逆元d,作为私钥指数。至此,公钥为(e, n),私钥为(d, n)。
  2. 加密/解密:对于明文m(需转换为整数且小于n),加密过程是计算密文 c = m^e mod n。解密则是计算 m = c^d mod n。
  3. 签名/验签:本质上是加密/解密过程的一种应用。签名时,对消息的摘要(如SHA256的结果)用私钥进行“加密”(即计算 s = hash^d mod n),得到签名值。验签时,用公钥对签名值进行“解密”(即计算 h' = s^e mod n),然后将结果与重新计算的消息摘要对比,一致则验签成功。

注意:实际中直接对原始数据运算存在安全风险(如明文猜测攻击),因此会采用PKCS#1 v1.5或OAEP等填充方案。OpenSSL默认使用PKCS#1 v1.5填充,这为我们处理了格式化和安全性问题。

2.2 OpenSSL中的RSA对象与内存管理

OpenSSL 1.1.1版本之后,其API设计更注重安全性和明确性。核心对象是RSA结构体,它封装了RSA密钥的所有组件(n, e, d, p, q等)。我们必须理解其生命周期管理:

  • 创建:使用RSA_new()函数分配一个空的RSA对象。
  • 生成密钥:使用RSA_generate_key_ex()函数生成密钥对并填充到RSA对象中。
  • 从文件加载:使用PEM读写函数,如PEM_read_RSAPrivateKey()PEM_read_RSA_PUBKEY()
  • 释放至关重要!必须使用RSA_free()来释放RSA对象,防止内存泄漏。这是C语言编程的纪律。

另一个关键对象是BIO(Basic Input/Output),它是OpenSSL的I/O抽象层,类似于文件指针,但可以关联内存、文件、套接字等。我们常用它来方便地进行PEM格式的密钥读写。

// 示例:创建一个BIO对象关联到内存缓冲区 BIO *bio = BIO_new(BIO_s_mem()); // ... 使用bio进行读写操作 BIO_free(bio); // 同样需要释放

理解这些对象及其管理方式是安全、正确使用OpenSSL库的前提。

3. 环境准备与OpenSSL库集成

3.1 OpenSSL库的获取与安装

对于Windows开发者,直接从“openssl官网下载”预编译库是最快的方式。请根据你的编译器(如VS的MSVC)选择32位或64位的版本。下载后,通常包含include文件夹(头文件)和lib文件夹(静态库或DLL的导入库)。

在Linux或macOS上,使用包管理器安装更为便捷,例如sudo apt-get install libssl-dev(Ubuntu/Debian)或brew install openssl(macOS)。安装后,头文件通常在/usr/include/openssl,库文件在/usr/lib/usr/local/ssl/lib

如果遇到“openssl' 不是内部或外部命令”的错误,说明你尝试在命令行运行openssl命令,但它的路径没有添加到系统的PATH环境变量中。对于编程使用库而言,这并不影响,我们只需要链接正确的库文件即可。

3.2 在C语言项目中配置OpenSSL

以常见的GCC(MinGW)或MSVC编译器为例,配置包含以下关键步骤:

  1. 包含头文件:在你的C源文件中,需要包含主要的头文件。
    #include <openssl/rsa.h> #include <openssl/pem.h> #include <openssl/err.h> #include <openssl/rand.h> #include <string.h> #include <stdio.h>
  2. 设置编译器包含路径:告诉编译器去哪里找openssl头文件。
    • GCC:-I/path/to/openssl/include
    • MSVC: 在项目属性 -> C/C++ -> 常规 -> 附加包含目录 中添加路径。
  3. 链接库文件:告诉链接器使用哪些库。
    • GCC:-L/path/to/openssl/lib -lssl -lcrypto。在Windows上,库文件可能是libssl.liblibcrypto.lib,使用-lssl -lcrypto即可。
    • MSVC: 在项目属性 -> 链接器 -> 输入 -> 附加依赖项 中添加libssl.lib;libcrypto.lib;,并在“链接器 -> 常规 -> 附加库目录”中添加库文件所在路径。

实操心得:在Windows下使用MSVC时,如果下载的是Win64 OpenSSL v1.1.1w这样的安装包,安装时选择“将OpenSSL DLL复制到系统目录”,可以避免后续运行时找不到libssl-1_1-x64.dll等动态链接库的问题。对于生产环境,更推荐将DLL与你的可执行文件放在一起。

4. 完整流程实现:从密钥生成到签名验证

4.1 步骤一:生成RSA密钥对

我们首先生成一对2048位的RSA密钥。这是目前公认的安全起点。

// 生成RSA密钥对示例 RSA *generate_rsa_keypair(int bits) { RSA *rsa = NULL; BIGNUM *bne = NULL; int ret = 0; // 1. 创建RSA结构体 rsa = RSA_new(); if (rsa == NULL) { fprintf(stderr, "RSA_new failed\n"); goto free_all; } // 2. 创建并设置公钥指数e(通常为65537) bne = BN_new(); ret = BN_set_word(bne, RSA_F4); // RSA_F4 就是65537 if (ret != 1) { fprintf(stderr, "BN_set_word failed\n"); goto free_all; } // 3. 生成密钥对 ret = RSA_generate_key_ex(rsa, bits, bne, NULL); if (ret != 1) { fprintf(stderr, "RSA_generate_key_ex failed\n"); goto free_all; } printf("RSA %d-bit key pair generated successfully.\n", bits); goto success; free_all: if (rsa) RSA_free(rsa); rsa = NULL; success: if (bne) BN_free(bne); return rsa; }

注意事项:RSA_generate_key_ex函数是线程安全的,而旧版的RSA_generate_key则不是。务必使用新API。密钥位数bits建议至少为2048,1024位已被认为不安全。

4.2 步骤二:将密钥对保存为PEM文件

生成的密钥需要持久化存储。PEM格式是常见的、可读的(Base64编码)格式。

// 保存RSA密钥到PEM文件 int save_rsa_key_to_file(RSA *rsa, const char *priv_key_file, const char *pub_key_file) { BIO *bp_priv = NULL, *bp_pub = NULL; int ret = 0; // 1. 保存私钥(包含公钥信息) bp_priv = BIO_new_file(priv_key_file, "w+"); if (bp_priv == NULL) { perror("Error opening private key file for writing"); goto free_all; } // PEM_write_RSAPrivateKey 使用默认加密算法(需要密码),如果想存为明文,最后一个参数用NULL ret = PEM_write_RSAPrivateKey(bp_priv, rsa, NULL, NULL, 0, NULL, NULL); if (ret != 1) { fprintf(stderr, "Error writing private key\n"); ERR_print_errors_fp(stderr); goto free_all; } printf("Private key saved to: %s\n", priv_key_file); // 2. 保存公钥 bp_pub = BIO_new_file(pub_key_file, "w+"); if (bp_pub == NULL) { perror("Error opening public key file for writing"); goto free_all; } // 注意:这里使用 PEM_write_RSA_PUBKEY,它写入的是 SubjectPublicKeyInfo 结构,更通用。 // PEM_write_RSAPublicKey 写入的是 PKCS#1 公钥,某些场景下兼容性稍差。 ret = PEM_write_RSA_PUBKEY(bp_pub, rsa); if (ret != 1) { fprintf(stderr, "Error writing public key\n"); ERR_print_errors_fp(stderr); goto free_all; } printf("Public key saved to: %s\n", pub_key_file); ret = 1; // 成功标志 free_all: if (bp_priv) BIO_free_all(bp_priv); if (bp_pub) BIO_free_all(bp_pub); return ret; }

4.3 步骤三:从文件加载RSA密钥

在实际应用中,我们更多是从存储的PEM文件中加载密钥。

// 从PEM文件加载RSA私钥和公钥 RSA *load_rsa_private_key(const char *priv_key_file) { BIO *bp = NULL; RSA *rsa = NULL; bp = BIO_new_file(priv_key_file, "r"); if (bp == NULL) { perror("Error opening private key file for reading"); return NULL; } // 如果私钥文件有密码,需要提供回调函数。这里假设无密码。 rsa = PEM_read_RSAPrivateKey(bp, NULL, NULL, NULL); if (rsa == NULL) { fprintf(stderr, "Error reading private key\n"); ERR_print_errors_fp(stderr); } BIO_free_all(bp); return rsa; } RSA *load_rsa_public_key(const char *pub_key_file) { BIO *bp = NULL; RSA *rsa = NULL; bp = BIO_new_file(pub_key_file, "r"); if (bp == NULL) { perror("Error opening public key file for reading"); return NULL; } // 对应 PEM_write_RSA_PUBKEY 的读取函数 rsa = PEM_read_RSA_PUBKEY(bp, NULL, NULL, NULL); if (rsa == NULL) { fprintf(stderr, "Error reading public key\n"); ERR_print_errors_fp(stderr); } BIO_free_all(bp); return rsa; }

4.4 步骤四:实现RSA公钥加密与私钥解密

这是非对称加密的经典场景:Alice用Bob的公钥加密信息,只有Bob用自己的私钥才能解密。

// RSA加密函数 int rsa_encrypt(RSA *rsa_pubkey, const unsigned char *plaintext, int plaintext_len, unsigned char *ciphertext) { // RSA_PKCS1_PADDING 是默认的填充方式 int result_len = RSA_public_encrypt(plaintext_len, plaintext, ciphertext, rsa_pubkey, RSA_PKCS1_PADDING); if (result_len == -1) { fprintf(stderr, "RSA_public_encrypt failed\n"); ERR_print_errors_fp(stderr); } return result_len; // 返回密文长度 } // RSA解密函数 int rsa_decrypt(RSA *rsa_privkey, const unsigned char *ciphertext, int ciphertext_len, unsigned char *decryptedtext) { int result_len = RSA_private_decrypt(ciphertext_len, ciphertext, decryptedtext, rsa_privkey, RSA_PKCS1_PADDING); if (result_len == -1) { fprintf(stderr, "RSA_private_decrypt failed\n"); ERR_print_errors_fp(stderr); } return result_len; // 返回解密后的明文长度 }

关键点解析

  • RSA_public_encryptRSA_private_decrypt是核心函数。
  • 输入数据(明文)的长度受限于密钥大小和填充方案。对于2048位密钥和PKCS#1 v1.5填充,最大明文长度约为密钥字节数 - 11(即256 - 11 = 245字节)。如果数据更长,需要采用“分段加密”或更常见的做法——使用RSA来加密一个对称密钥(如AES密钥),然后用对称密钥加密大量数据。
  • 输出缓冲区(ciphertextdecryptedtext)必须足够大,至少为RSA_size(rsa)字节。

4.5 步骤五:实现RSA数字签名与验证

数字签名用于验证数据的完整性和来源真实性。流程是:发送方对数据的哈希值用私钥签名,接收方用公钥验证签名。

// 使用SHA256进行RSA签名 int rsa_sign(RSA *rsa_privkey, const unsigned char *msg, size_t msg_len, unsigned char *signature, unsigned int *sig_len) { unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256_CTX sha_ctx; // 1. 计算消息的SHA256哈希值 if (!SHA256_Init(&sha_ctx) || !SHA256_Update(&sha_ctx, msg, msg_len) || !SHA256_Final(hash, &sha_ctx)) { fprintf(stderr, "SHA256 calculation failed\n"); return 0; } // 2. 对哈希值进行签名 if (RSA_sign(NID_sha256, hash, SHA256_DIGEST_LENGTH, signature, sig_len, rsa_privkey) != 1) { fprintf(stderr, "RSA_sign failed\n"); ERR_print_errors_fp(stderr); return 0; } return 1; } // 验证RSA签名 int rsa_verify(RSA *rsa_pubkey, const unsigned char *msg, size_t msg_len, const unsigned char *signature, unsigned int sig_len) { unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256_CTX sha_ctx; // 1. 重新计算消息的SHA256哈希值 if (!SHA256_Init(&sha_ctx) || !SHA256_Update(&sha_ctx, msg, msg_len) || !SHA256_Final(hash, &sha_ctx)) { fprintf(stderr, "SHA256 calculation failed during verification\n"); return 0; } // 2. 验证签名 if (RSA_verify(NID_sha256, hash, SHA256_DIGEST_LENGTH, signature, sig_len, rsa_pubkey) != 1) { fprintf(stderr, "RSA_verify failed: Invalid signature!\n"); ERR_print_errors_fp(stderr); return 0; } printf("Signature verification SUCCESSFUL.\n"); return 1; }

关键点解析

  • 数字签名不是直接对原始消息用私钥加密,而是对消息的摘要(哈希值)进行加密。这里我们选用SHA256作为哈希算法。
  • RSA_signRSA_verify函数内部会自动处理PKCS#1等填充格式。
  • 签名长度*sig_lenRSA_sign函数填充,通常是RSA_size(rsa)的值。
  • 验证成功返回1,失败返回0。务必检查返回值。

5. 整合演示与核心参数说明

让我们将上述所有步骤串联起来,形成一个完整的演示程序。

int main() { // 初始化OpenSSL(重要!尤其是为了随机数生成) OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); const char *priv_file = "private_key.pem"; const char *pub_file = "public_key.pem"; RSA *my_rsa = NULL; RSA *loaded_pubkey = NULL; RSA *loaded_privkey = NULL; // 场景1:生成并保存密钥 printf("=== 1. Generating and Saving Keys ===\n"); my_rsa = generate_rsa_keypair(2048); if (!my_rsa) goto cleanup; if (!save_rsa_key_to_file(my_rsa, priv_file, pub_file)) goto cleanup; RSA_free(my_rsa); // 生成完毕,释放内存 my_rsa = NULL; // 场景2:加载密钥并进行加密解密 printf("\n=== 2. Encryption & Decryption Demo ===\n"); loaded_pubkey = load_rsa_public_key(pub_file); loaded_privkey = load_rsa_private_key(priv_file); if (!loaded_pubkey || !loaded_privkey) goto cleanup; const char *original_msg = "This is a secret message for RSA encryption!"; int msg_len = strlen(original_msg); int rsa_size = RSA_size(loaded_pubkey); unsigned char ciphertext[4096] = {0}; unsigned char decryptedtext[4096] = {0}; int cipher_len = rsa_encrypt(loaded_pubkey, (unsigned char*)original_msg, msg_len, ciphertext); if (cipher_len <= 0) goto cleanup; printf("Encryption done. Ciphertext length: %d bytes\n", cipher_len); int decrypted_len = rsa_decrypt(loaded_privkey, ciphertext, cipher_len, decryptedtext); if (decrypted_len <= 0) goto cleanup; decryptedtext[decrypted_len] = '\0'; // 添加字符串结束符 printf("Decryption done. Decrypted text: %s\n", decryptedtext); // 场景3:数字签名与验证 printf("\n=== 3. Digital Signature & Verification Demo ===\n"); const char *document = "Important contract content to be signed."; size_t doc_len = strlen(document); unsigned char signature[4096] = {0}; unsigned int sig_len = 0; if (!rsa_sign(loaded_privkey, (unsigned char*)document, doc_len, signature, &sig_len)) goto cleanup; printf("Signing done. Signature length: %u bytes\n", sig_len); if (!rsa_verify(loaded_pubkey, (unsigned char*)document, doc_len, signature, sig_len)) goto cleanup; // 篡改消息,验证应失败 printf("\n=== 4. Testing with Tampered Data ===\n"); const char *tampered_doc = "Tampered contract content to be signed."; if (rsa_verify(loaded_pubkey, (unsigned char*)tampered_doc, strlen(tampered_doc), signature, sig_len)) { printf("ERROR: Signature verification should have failed for tampered data!\n"); } else { printf("As expected, signature verification failed for tampered data.\n"); } cleanup: if (my_rsa) RSA_free(my_rsa); if (loaded_pubkey) RSA_free(loaded_pubkey); if (loaded_privkey) RSA_free(loaded_privkey); // 清理OpenSSL全局状态 EVP_cleanup(); ERR_free_strings(); return 0; }

核心参数与缓冲区管理总结表

操作关键函数输入缓冲区大小输出缓冲区大小关键限制
加密RSA_public_encrypt明文长度 ≤RSA_size(pubkey)-11必须 ≥RSA_size(pubkey)填充方式影响最大明文长度
解密RSA_private_decrypt密文长度 =RSA_size(privkey)必须 ≥RSA_size(privkey)输入必须是正确格式的密文
签名RSA_sign哈希值长度(如SHA256是32)必须 ≥RSA_size(privkey)函数自动处理填充,输入是哈希值
验签RSA_verify哈希值长度(如SHA256是32)无(签名值作为输入)函数自动处理填充和比较

6. 常见问题、错误排查与进阶技巧

6.1 编译与链接问题

  • **“undefined reference toRSA_new’…”**:这是最典型的链接错误,说明编译器找到了头文件,但链接器没找到库文件。请仔细检查-lssl -lcrypto参数是否正确,以及库文件路径(-L`或MSVC的附加库目录)是否设置正确。
  • “PEM_read_RSAPrivateKey failed”:首先检查文件路径和权限。如果私钥文件是加密的(有密码),你需要提供一个密码回调函数给PEM_read_RSAPrivateKey。如果密码错误或回调函数返回空,也会失败。可以使用ERR_print_errors_fp(stderr);打印详细的OpenSSL错误堆栈,这是调试的金钥匙。

6.2 运行时错误与数据错误

  • “RSA_public_encrypt: data too large for key size”:明文数据超过了当前密钥和填充模式允许的最大长度。对于长数据,必须采用“混合加密”方案:生成一个随机AES密钥,用RSA公钥加密这个AES密钥,然后用AES密钥加密实际数据。接收方则先用RSA私钥解密出AES密钥,再用AES密钥解密数据。
  • “RSA_verify failed”:验签失败的原因有多种:
    1. 签名被篡改:这是正常的安全机制触发。
    2. 使用的公钥与签名私钥不配对:这是最常见的原因之一。确保加载的公钥文件与签名者使用的私钥是对应的。
    3. 哈希算法不匹配:签名时用了SHA256,验签时也必须用SHA256。NID_sha256这个标识符必须一致。
    4. 数据在传输过程中被修改:用于验签的原始消息必须与签名时的消息完全一致,哪怕一个字节不同,哈希值就天差地别。
  • “digital envelope routines:initialization error”:这类错误通常与底层算法上下文初始化有关,确保在程序开始调用了OpenSSL_add_all_algorithms()

6.3 安全与性能实践

  1. 密钥管理:私钥是最高机密,必须妥善保管。生产环境中,私钥不应以明文形式存储在代码或普通文件中,应考虑使用硬件安全模块(HSM)或操作系统提供的密钥保管箱(如Windows DPAPI, Linux Keyring)。
  2. 随机数质量:密钥生成依赖于随机数。RSA_generate_key_ex内部使用了OpenSSL的随机数发生器。确保系统有足够的熵源(如/dev/urandom)。在虚拟机或容器中,有时需要检查熵池是否充足。
  3. 填充方案选择:我们使用的RSA_PKCS1_PADDING(即PKCS#1 v1.5)虽然广泛支持,但在某些特定场景下可能存在风险(如Bleichenbacher攻击)。对于新系统,更推荐使用RSA_PKCS1_OAEP_PADDING(最优非对称加密填充)进行加密,它安全性更好。签名则推荐使用PSS填充(RSA_signRSA_verify支持指定),可以通过RSA_sign_pss_mgf1RSA_verify_pss_mgf1等函数使用。
  4. 错误处理:OpenSSL函数失败时,往往只是返回-1或0。务必使用ERR_print_errors_fp(stderr);将错误队列打印出来,这是定位问题最有效的手段。
  5. 内存泄漏检查:C语言需要手动管理内存。确保每个RSA_new(),BIO_new()都有对应的RSA_free(),BIO_free_all()。可以使用Valgrind等工具进行内存泄漏检测。

6.4 从文件读取密钥的另一种方式

有时你可能需要从字符串(比如一个配置变量)中加载PEM格式的密钥,而不是从文件。这时可以使用BIO_new_mem_buf

// 从字符串加载公钥示例 RSA *load_pubkey_from_string(const char *pem_string) { BIO *bio = BIO_new_mem_buf(pem_string, -1); // -1 表示字符串以NULL结尾 if (bio == NULL) return NULL; RSA *rsa = PEM_read_RSA_PUBKEY(bio, NULL, NULL, NULL); BIO_free_all(bio); return rsa; }

这个技巧在将密钥硬编码到代码(不推荐用于生产环境)或从网络配置中读取时非常有用。

走完这一整套流程,你应该对如何在C语言中驾驭OpenSSL进行RSA操作有了扎实的理解。记住,密码学是精细活,任何一个参数的误用都可能导致安全漏洞或功能失效。多动手测试,善用错误输出,在理解原理的基础上谨慎实践。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/23 21:45:04

从IDOR到权限校验:一次完整的越权漏洞挖掘实战与修复指南

1. 项目概述&#xff1a;一次不经意的越权漏洞挖掘 那天下午&#xff0c;我正像往常一样&#xff0c;对一个内部测试环境的后台管理系统进行常规的功能测试。我的任务很简单&#xff0c;就是验证几个新上线的用户权限管理功能是否正常。我登录了一个普通员工的测试账号&#xf…

作者头像 李华
网站建设 2026/6/23 21:26:36

Linux 【06-head命令超详细教程】

Linux head 命令超详细保姆级教程 一、命令作用 head 用于查看文件开头内容&#xff0c;默认打印文件前10行&#xff1b;也可接收管道输出&#xff0c;截取命令输出的头部数据&#xff0c;日常排查日志、读取配置、过滤输出高频使用。 二、基础语法 head [选项] 文件名 # 管道用…

作者头像 李华
网站建设 2026/6/23 21:26:19

单头双平台脉冲热压机

1.总机采用铝合金框架&#xff0c;表面电泳处理&#xff0c;美观且不掉色&#xff0c;耐高温&#xff1b; 2. 平台行程&#xff1a;X1/X2:300mm,Y1/N2:300 Z1/Z2:100mm; 3. 平台速度&#xff1a; X:0.1-500mm/S,Y:0.1-500mm/S,Z:0.01-500mm/S; 4. 平台精度&#xff1a; …

作者头像 李华
网站建设 2026/6/23 21:25:05

盟接之桥:看似简单实则关键,EDI对接前必须厘清的四大核心问题

在全球制造业加速迈向数字化、智能化转型的宏大叙事中&#xff0c;供应链的韧性与响应速度已成为企业核心竞争力的重要组成部分。对于广大出口导向型制造企业而言&#xff0c;如何跨越地理与系统的鸿沟&#xff0c;与国内外客户实现生产数据、品质数据等关键业务信息的高效、准…

作者头像 李华
网站建设 2026/6/23 21:22:24

C#:正则表达式与有限性验证

在C#中&#xff0c;使用正则表达式&#xff08;Regular Expressions&#xff09;来限制控件输入的有效性是一个常见需求&#xff0c;尤其是在处理用户输入时。正则表达式提供了一种强大的方式来定义输入格式&#xff0c;如电子邮件地址、电话号码、邮政编码等。以下是一些步骤和…

作者头像 李华
网站建设 2026/6/23 21:21:53

ADM云GPU私有化部署Z-Image模型+ComfyUI远程访问

​ 上次我们介绍了在云切片上私有部署 LTC-Video 开源大模型的方法&#xff0c;很多朋友说不能用 ComfyUI&#xff0c;也不能远程访问。其实用点小技巧是可以的&#xff0c;不仅可以用 ComfyUI&#xff0c;也可以通过 API 远程访问云切片上的大模型。这次我们就用开源 Z-Image …

作者头像 李华