上次一文精通数字证书的签名算法讨论了证书签名算法,公钥类型和证书链等。这时脑后有反骨的朋友可能会说,“既然知道结构,又知道字段的生成算法,可不可以把我的名字写到签名值上呢?或者嵌入我自己发明的公钥算法?”。本文对这些问题逐一进行解答
目录
- 修改证书的公钥
- 示例操作:生成x25519类型的数字证书
-
修改证书的签名
- 示例操作:把证书的签名值清零
一、修改证书的公钥
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import ec, x25519
from cryptography.x509 import (
Certificate, NameOID, Name, SubjectAlternativeName,
BasicConstraints, KeyUsage, ExtendedKeyUsage,
CertificateSigningRequestBuilder,
random_serial_number,
load_pem_x509_certificate,
CertificateBuilder,ObjectIdentifier,
NameAttribute
)
from cryptography.x509.oid import ExtensionOID
from datetime import datetime, timedelta
from cryptography.hazmat.backends import default_backend
# 生成临时 X25519 密钥对
temp_server_x_private_key = x25519.X25519PrivateKey.generate()
temp_server_x_public_key = temp_server_x_private_key.public_key()
temp_server_x_public_bytes = temp_server_x_public_key.public_bytes_raw()
# ========= 签发证书 =========
# 加载中间 CA 私钥和证书
with open("intermediate_2_ec_private.key", "rb") as f:
ca_private_key = serialization.load_pem_private_key(
f.read(), password=None
)
with open("intermediate_2_ec_cert.pem", "rb") as f:
ca_cert =load_pem_x509_certificate(f.read())
# 构建 X.509 证书模板
cert_builder = (
CertificateBuilder()
.subject_name(Name([
NameAttribute(NameOID.COMMON_NAME, u"My X25519 Server") # 设置主题
]))
.issuer_name(ca_cert.subject) # 颁发者为 CA 的主题
.public_key(temp_server_x_public_key) # 自定义数据 TODO。现在是Public Key Algorithm: id-ecPublicKey
.serial_number(random_serial_number())
.not_valid_before(datetime.utcnow())
.not_valid_after(datetime.utcnow() + timedelta(days=90))
.add_extension(
BasicConstraints(ca=False, path_length=None),
critical=True
)
.add_extension(
KeyUsage(
digital_signature=False,
key_encipherment=False,
key_agreement=True, # X25519 密钥协商
content_commitment=False,
data_encipherment=False,
key_cert_sign=False,
crl_sign=False,
encipher_only=False,
decipher_only=False
),
critical=True
)
.add_extension(
ExtendedKeyUsage([ObjectIdentifier("1.3.101.110")]),
critical=False
)
)
# 使用中间 CA 签发证书
certificate = cert_builder.sign(
private_key=ca_private_key,
algorithm=hashes.SHA256(),
)
certificate = cert_builder.sign(
private_key=ca_private_key,
algorithm=hashes.SHA3_512(), # 可以使用 SHA224,SHA384,SHA3_224,SHA3_256,SHA3_384,SHA3_512
backend=default_backend()
)
# 序列化证书
cert_pem = certificate.public_bytes(serialization.Encoding.PEM)
with open("x25519_SHA3_512_tmp_certificate.pem", "wb") as pem_file:
pem_file.write(cert_pem)
该证书使用了x25519作为公钥,使用SHA3-512作为签名的hash算法,低版本的openssl可能不支持验签,需要提高openssl版本或编程验签,否则会报下面的算法不支持异常。
error 7 at 0 depth lookup: certificate signature failure
error transaction_4_SHA3_512_tmp_certificate.pem: verification failed
139976144574272:error:0D0C50C7:asn1 encoding routines:ASN1_item_verify:unknown signature algorithm:crypto/asn1/a_verify.c:114
工程中可能的用处
首先这个证书的特点如下。
-
x25519是专门为密钥交换设计的,即数据加密。
-
x25519证书无法签发子证书,如果强制签发会因算法没有签名功能而报错。这也就意味着x25519证书一定位于证书链的终结点。
x25519证书比较适合一次性临时证书的分发。例如通信双方通信前先进行秘钥协商,客户端发送x25519临时公钥到服务端,客户端使用证书链对x25519公钥进行完整性保护,根CA证书在服务端预置。这个设计同时完成了x25519协商公钥传递、公钥完整性保护、客户端身份证明3步操作。另外由于x25519证书是程序即时生成的,还可以加入其他自定义信息例如一次性challenge token一起组成签名数据,提供防重放功能。更多应用场景欢迎讨论和自由发挥。
二、修改证书的签名
修改签名无法通过常规证书签发手段完成,只能通过C++调用openssl底层库,试过python和golang的证书库都无法调用低级openssl函数。这里演示一下如何将RSA类型证书的签名值全部清空。
voidhandle_openssl_error(){
ERR_print_errors_fp(stderr);
abort();
}
intmain(){
//初始化 OpenSSL
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
ERR_load_crypto_strings();
//创建 RSA 密钥对
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
if (!ctx) handle_openssl_error();
if (EVP_PKEY_keygen_init(ctx) <= 0) handle_openssl_error();
if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) <= 0) handle_openssl_error();
EVP_PKEY* pkey = NULL;
if (EVP_PKEY_keygen(ctx, &pkey) <= 0) handle_openssl_error();
EVP_PKEY_CTX_free(ctx);
//创建 X509 证书
X509* x509 = X509_new();
if (!x509) handle_openssl_error();
//设置版本号 (v3)
X509_set_version(x509, 2);
//设置序列号
ASN1_INTEGER_set(X509_get_serialNumber(x509), 123456789);
//设置有效期:从现在开始 365 天
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L); // 365 days
//设置公钥
if (!X509_set_pubkey(x509, pkey)) handle_openssl_error();
//设置主题和颁发者(相同)
X509_NAME* name = X509_get_subject_name(x509);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*)"Test Cert", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*)"MyOrg", -1, -1, 0);
X509_set_issuer_name(x509, name);
//使用 SHA-256 算法进行签名
const EVP_MD* digest = EVP_sha256();
//正常签名一次以生成签名字段
if (!X509_sign(x509, pkey, digest)) handle_openssl_error();
//获取签名长度
const ASN1_BIT_STRING* signature;
const X509_ALGOR* sig_alg;
X509_get0_signature(&signature, &sig_alg, x509);
int sig_len = signature->length;
unsigned char* zero_sig = new unsigned char[sig_len];
std::memset(zero_sig, 0, sig_len);
//替换签名字段为全 0
ASN1_BIT_STRING* mutable_sig = const_cast<ASN1_BIT_STRING*>(signature);
ASN1_BIT_STRING_set(mutable_sig, zero_sig, sig_len);
delete[] zero_sig;
//输出 PEM 格式的证书
BIO* bio = BIO_new(BIO_s_mem());
PEM_write_bio_X509(bio, x509);
BUF_MEM* mem;
BIO_get_mem_ptr(bio, &mem);
BIO_set_close(bio, BIO_NOCLOSE);
BIO_free(bio);
std::cout << "Generated certificate with zeroed signature:" << std::endl;
std::cout.write((char*)mem->data, mem->length);
std::cout << std::endl;
//清理资源
BUF_MEM_free(mem);
X509_free(x509);
EVP_PKEY_free(pkey);
return 0;
}
sudo apt install libssl-dev
g++ -o generate_ecc_cert generate_ecc_cert.cpp -lssl -lcrypto
微信逆向解密聊天记录[附详细步骤]
原文始发于微信公众号(青木生长):如何把肯德基订餐电话写到证书的签名值和公钥数据里
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论