使用 base64 public 密钥 NID_secp384r1 和 openSSL 验证签名
Verify Signature with a base64 public key NID_secp384r1 with openSSL
我正在尝试使用 openssl 1.1.1k 验证签名,但我无法导入我使用 SubtleCrypto Web Crypto API 生成的 DER 编码 SPKI 格式 public 密钥。
使用 https://holtstrom.com/ 解码 public 密钥:
SEQUENCE {
SEQUENCE {
OBJECTIDENTIFIER 1.2.840.10045.2.1 (ecPublicKey)
OBJECTIDENTIFIER 1.3.132.0.34 (P-384)
}
BITSTRING 0x04b8546c630d4c48195e071d109d36ecbddf328274c6882f6f9de9c112d8691e5428f08baeee7d2f2bf4ea888f759d5313cd4f1ed14862f1d5a24f69520242b116702cc5e573bc7deb392042b8a3a8f00e13f90e69f9c45a8b0ce60aae2c74dcef : 0 unused bit(s)
}
我 3 次尝试的 C++ 代码。我还在问题发生的行旁边评论了 openssl 库中的错误及其字符串表示形式:
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
//include OpenSSL headers
#include <cassert>
#include <openssl/sha.h>
#include <openssl/bn.h>
#include <openssl/hmac.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/obj_mac.h>
#include <openssl/opensslv.h>
#include <openssl/engine.h>
#include <openssl/ssl.h>
#include <sstream>
#include <vector>
#include <openssl/err.h>
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static inline bool is_base64(unsigned char c) {
return (isalnum(c) || (c == '+') || (c == '/'));
}
namespace {
std::string officalHash = "MTY0MjY5ODgyMg==";
std::string officalSignature = "YkTPCGVj1Su+b4cqrOQPSwxHh79BTd9HZk/0OH71HjIbQ8/lSuKOEeNcpY9O7+4vgabIDRlyH5QmZmMV7X9s8eCk4cU7RAfn2YwE2pSvoik+upILLS9qmIDSaDz6LU2x";
const auto officalPublicKey = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEuFRsYw1MSBleBx0QnTbsvd8ygnTGiC9vnenBEthpHlQo8Iuu7n0vK/TqiI91nVMTzU8e0Uhi8dWiT2lSAkKxFnAsxeVzvH3rOSBCuKOo8A4T+Q5p+cRaiwzmCq4sdNzv";
struct BIOFreeAll { void operator()(BIO* p) { BIO_free_all(p); } };
}
std::string base64_decode(std::string const& encoded_string);
void tryOne(const std::vector<uint8_t>& binaryPublicKey);
void tryTwo(const std::vector<uint8_t>& binaryPublicKey);
void tryThree(const std::vector<uint8_t>& binaryPublicKey);
int main()
{
SSL_library_init();
SSL_load_error_strings();
const auto hashBytes = base64_decode(officalHash);
const auto publicKeyTemp = officalPublicKey;
std::unique_ptr<BIO, BIOFreeAll> b64(BIO_new(BIO_f_base64()));
BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
BIO* source = BIO_new_mem_buf(officalPublicKey, -1); // read-only source
BIO_push(b64.get(), source);
const int maxlen = strlen(officalPublicKey) / 4 * 3 + 1;
std::vector<uint8_t> decoded(maxlen);
const int len = BIO_read(b64.get(), decoded.data(), maxlen);
decoded.resize(len);
tryOne(decoded);
tryTwo(decoded);
tryThree(decoded);
return 0;
}
void tryOne(const std::vector<uint8_t>& binaryPublicKey)
{
EVP_PKEY* key;
EC_KEY* ec_key = EC_KEY_new_by_curve_name(NID_secp384r1);
EVP_PKEY* ret = EVP_PKEY_new();
EVP_PKEY_assign_EC_KEY(ret, ec_key);
unsigned char const* ptr = binaryPublicKey.data();
key = d2i_PublicKey(EVP_PKEY_EC, &ret, &ptr, binaryPublicKey.size());
if (!key) {
std::string error4 = ERR_error_string(ERR_get_error(), nullptr); //<- Error: error:10067066:elliptic curve routines:ec_GFp_simple_oct2point:invalid encoding
return;
}
}
void tryTwo(const std::vector<uint8_t>& binaryPublicKey)
{
const auto tempstr = base64_decode(officalSignature);
std::vector<uint8_t> SignatureBytes(tempstr.begin(), tempstr.end());
std::shared_ptr<EVP_MD_CTX> mdctx = std::shared_ptr<EVP_MD_CTX>(EVP_MD_CTX_create(), EVP_MD_CTX_free);
const EVP_MD* md = EVP_get_digestbyname("SHA512");
if (nullptr == md)
{
return;
}
if (0 == EVP_VerifyInit_ex(mdctx.get(), md, NULL))
{
return;
}
if (0 == EVP_VerifyUpdate(mdctx.get(), binaryPublicKey.data(), binaryPublicKey.size()))
{
return;
}
std::shared_ptr<BIO> b644 = std::shared_ptr<BIO>(BIO_new(BIO_f_base64()), BIO_free);
BIO_set_flags(b644.get(), BIO_FLAGS_BASE64_NO_NL);
std::shared_ptr<BIO> bPubKey = std::shared_ptr<BIO>(BIO_new(BIO_s_mem()), BIO_free);
BIO_puts(bPubKey.get(), officalPublicKey);
BIO_push(b644.get(), bPubKey.get());
std::shared_ptr<EVP_PKEY> pubkey = std::shared_ptr<EVP_PKEY>(d2i_PUBKEY_bio(b644.get(), NULL), EVP_PKEY_free);
if (!pubkey) {
std::string error = ERR_error_string(ERR_get_error(), nullptr);
return;
}
auto b = EVP_VerifyFinal(mdctx.get(), SignatureBytes.data(), SignatureBytes.size(), pubkey.get());
std::string error = ERR_error_string(ERR_get_error(), nullptr); // <- Error: error:0D0680A8:asn1 encoding routines:asn1_check_tlen:wrong tag
}
void tryThree(const std::vector<uint8_t>& binaryPublicKey)
{
EC_KEY* eckey = NULL;
std::vector<uint8_t> pubKeyVC = binaryPublicKey;
const unsigned char* pubKeyVCp = pubKeyVC.data();
const unsigned char** pubKeyVCpp = &pubKeyVCp;
eckey = EC_KEY_new_by_curve_name(NID_secp384r1);
EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
eckey = o2i_ECPublicKey(&eckey, pubKeyVCpp, pubKeyVC.size());
if (!EC_KEY_check_key(eckey)) {
std::string error = ERR_error_string(ERR_get_error(), nullptr); //<- Error: "error:0D07803A:asn1 encoding routines:asn1_item_embed_d2i:nested asn1 error"
}
}
std::string base64_decode(std::string const& encoded_string) {
int in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
char_array_4[i++] = encoded_string[in_]; in_++;
if (i == 4) {
for (i = 0; i < 4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}
if (i) {
for (j = i; j < 4; j++)
char_array_4[j] = 0;
for (j = 0; j < 4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}
return ret;
}
密钥生成:
public 密钥是在 javascript:
中使用 SubtleCrypto 生成的
function generateEcdsaKeypair() {
let keypair = window.crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-384"
},
true,
["sign", "verify"]
)
return keypair
}
然后使用 RFC 5280 第 4.1 节中使用 ASN.1 表示法定义的 SubjectPublicKeyInfo 格式导出:
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
async function exportCryptoKey(key) {
const exported = await window.crypto.subtle.exportKey(
"spki",
key
);
const exportedAsString = ab2str(exported);
const exportedAsBase64 = window.btoa(exportedAsString);
console.log("PublicKey: ", exportedAsBase64);
}
为了签署数据,我使用了以下函数:
function signEcdsa(privateKey, data) {
let signature = window.crypto.subtle.sign(
{
name: 'ECDSA',
hash: { name: 'SHA-512' },
},
privateKey,
data
)
return signature
}
但我什至无法将 base64 字符串转换为 public 密钥。验证更不用说了
整代档案:
js.html 文件:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Programiz</title>
</head>
<body>
<script src="js.js"></script>
</body>
</html>
js.js 文件:
可以在控制台选项卡下的检查 window 中找到输出。
function generateEcdsaKeypair() {
let keypair = window.crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-384"
},
true,
["sign", "verify"]
)
return keypair
}
// Generates a signature of the data given
function signEcdsa(privateKey, data) {
let signature = window.crypto.subtle.sign(
{
name: 'ECDSA',
hash: { name: 'SHA-512' },
},
privateKey,
data
)
return signature
}
function verifyEcdsa(publicKey, data, signature) {
let result = window.crypto.subtle.verify(
{
name: 'ECDSA',
hash: { name: 'SHA-512' },
},
publicKey,
signature,
data
)
return result
}
function _arrayBufferToBase64( buffer ) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa( binary );
}
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
function toHexString(byteArray) {
return Array.from(byteArray, function(byte) {
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('')
}
function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
return String.fromCharCode('0x' + p1);
}));
}
function b64DecodeUnicode(str) {
return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
function addNewLines(str) {
var finalString = '';
for(var i=0; i < str.length; i++) {
finalString += str.substring(0, 64) + '\n';
str = str.substring(64);
}
finalString += str;
return finalString;
}
function removeLines(pem) {
var lines = pem.split('\n');
var encodedString = '';
for(var i=0; i < lines.length; i++) {
encodedString += lines[i].trim();
}
return encodedString;
}
function stringToArrayBuffer(byteString){
var byteArray = new Uint8Array(byteString.length);
for(var i=0; i < byteString.length; i++) {
byteArray[i] = byteString.codePointAt(i);
}
return byteArray;
}
function arrayBufferToString(exportedPrivateKey){
var byteArray = new Uint8Array(exportedPrivateKey);
var byteString = '';
for(var i=0; i < byteArray.byteLength; i++) {
byteString += String.fromCodePoint(byteArray[i]);
}
return byteString;
}
async function exportCryptoKey(key) {
const exported = await window.crypto.subtle.exportKey(
"spki",
key
);
const exportedAsString = ab2str(exported);
const exportedAsBase64 = window.btoa(exportedAsString);
console.log("PublicKey: ", exportedAsBase64);
console.log(toHexString(exported));
var privateKeyDer = arrayBufferToString(exported); //pkcs#8 to DER
var privateKeyB64 = b64EncodeUnicode(privateKeyDer); //btoa(privateKeyDer);
var privateKeyPEMwithLines = addNewLines(privateKeyB64); //split PEM into 64 character strings
var privateKeyPEMwithoutLines = removeLines(privateKeyPEMwithLines); //join PEM
var privateKeyDerDecoded = b64DecodeUnicode(privateKeyPEMwithoutLines); // atob(privateKeyB64);
var privateKeyArrayBuffer = stringToArrayBuffer(privateKeyDerDecoded); //DER to arrayBuffer
console.log(exported);
console.log(privateKeyDer);
console.log(privateKeyB64);
console.log(privateKeyPEMwithLines);
console.log(privateKeyPEMwithoutLines);
console.log(privateKeyDerDecoded);
console.log(privateKeyArrayBuffer);
/*const pemExported = `-----BEGIN PUBLIC KEY-----\n${exportedAsBase64}\n-----END PUBLIC KEY-----`;
const exportKeyOutput = document.querySelector(".exported-key");
exportKeyOutput.textContent = pemExported;*/
}
function arrayBufferToString(exportedPrivateKey){
var byteArray = new Uint8Array(exportedPrivateKey);
var byteString = '';
for(var i=0; i < byteArray.byteLength; i++) {
byteString += String.fromCodePoint(byteArray[i]);
}
return byteString;
}
async function testSignEcdsa() {
let {
privateKey: aPrivate,
publicKey: aPublic
} = await generateEcdsaKeypair()
// A sends and signs data for B using their own private key
let data = window.crypto.getRandomValues(new Uint8Array(64))
let signature = await signEcdsa(aPrivate, data)
// B verifies A's signature using A's public key
let result = await verifyEcdsa(aPublic, data, signature)
let base64result = _arrayBufferToBase64(signature)
let signatureBase64 = window.btoa(String.fromCharCode.apply(null, new Uint8Array(signature)))
let dataBase64 = window.btoa(String.fromCharCode.apply(null, new Uint8Array(data)))
// Tests
// Signature is verified
console.log('Data signed/verified successfully: ', result)
console.log('Signature: ', signatureBase64)
console.log('Data: ', dataBase64)
exportCryptoKey(aPublic);
}
testSignEcdsa()
更新:
我把它放在这里是为了让其他人不需要通过 openssl 的文档来了解如何将 r|s 签名转换为 ans1 der 编码。不要忘记检查错误代码:
std::vector<uint8_t> Vault::convertP1363EncodingSignatureToASN1Der(const std::string& signatureInHexFormat) const
{ std::unique_ptr< ECDSA_SIG, std::function<void(ECDSA_SIG*)>> zSignature(ECDSA_SIG_new(), [](ECDSA_SIG* b) { ECDSA_SIG_free(b); });
std::unique_ptr< BIGNUM, std::function<void(BIGNUM*)>> r(nullptr, [](BIGNUM* b) { BN_free(b); });
BIGNUM* r_ptr = r.get();
std::unique_ptr< BIGNUM, std::function<void(BIGNUM*)>> s(nullptr, [](BIGNUM* b) { BN_free(b); });
BIGNUM* s_ptr = s.get();
const std::string sSignatureR = signatureInHexFormat.substr(0, signatureInHexFormat.size() / 2);
const std::string sSignatureS = signatureInHexFormat.substr(signatureInHexFormat.size() / 2);
BN_hex2bn(&r_ptr, sSignatureR.c_str());
BN_hex2bn(&s_ptr, sSignatureS.c_str());
ECDSA_SIG_set0(zSignature.get(), r_ptr, s_ptr);
unsigned char buffer[256];
unsigned char* pbuffer = buffer;
int signatureLength = i2d_ECDSA_SIG(zSignature.get(), nullptr);
signatureLength = i2d_ECDSA_SIG(zSignature.get(), &pbuffer);
return { buffer, buffer + signatureLength };
}
tryTwo()
允许通过以下更改成功验证发布的数据:
除了密钥和签名外,消息本身也需要验证。但是,在当前代码中根本没有使用该消息。必须在 VerifyUpdate()
中指定(而不是 public 键):
void tryTwo(const std::vector<uint8_t>& binaryPublicKey)
{
const auto hashBytes = base64_decode(officalHash);
...
if (0 == EVP_VerifyUpdate(mdctx.get(), hashBytes.c_str(), hashBytes.size()))
{
...
顺便说一句,officalHash
和 hashBytes
是误导性的名称,因为应用了 未散列 消息。
WebCrypto 生成 IEEE P1363 (r|s) 格式的签名,而 EVP_VerifyFinal()
需要 ASN.1/DER 格式的签名。发布的 ASN.1/DER 格式签名为(Base64 编码):
std::string officalSignature = "MGUCMGJEzwhlY9Urvm+HKqzkD0sMR4e/QU3fR2ZP9Dh+9R4yG0PP5UrijhHjXKWPTu/uLwIxAIGmyA0Zch+UJmZjFe1/bPHgpOHFO0QH59mMBNqUr6IpPrqSCy0vapiA0mg8+i1NsQ==";
解释了两种格式之间的关系,例如here.
经过这两个改动,在我的机器上验证成功。
我正在尝试使用 openssl 1.1.1k 验证签名,但我无法导入我使用 SubtleCrypto Web Crypto API 生成的 DER 编码 SPKI 格式 public 密钥。
使用 https://holtstrom.com/ 解码 public 密钥:
SEQUENCE {
SEQUENCE {
OBJECTIDENTIFIER 1.2.840.10045.2.1 (ecPublicKey)
OBJECTIDENTIFIER 1.3.132.0.34 (P-384)
}
BITSTRING 0x04b8546c630d4c48195e071d109d36ecbddf328274c6882f6f9de9c112d8691e5428f08baeee7d2f2bf4ea888f759d5313cd4f1ed14862f1d5a24f69520242b116702cc5e573bc7deb392042b8a3a8f00e13f90e69f9c45a8b0ce60aae2c74dcef : 0 unused bit(s)
}
我 3 次尝试的 C++ 代码。我还在问题发生的行旁边评论了 openssl 库中的错误及其字符串表示形式:
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
//include OpenSSL headers
#include <cassert>
#include <openssl/sha.h>
#include <openssl/bn.h>
#include <openssl/hmac.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/obj_mac.h>
#include <openssl/opensslv.h>
#include <openssl/engine.h>
#include <openssl/ssl.h>
#include <sstream>
#include <vector>
#include <openssl/err.h>
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static inline bool is_base64(unsigned char c) {
return (isalnum(c) || (c == '+') || (c == '/'));
}
namespace {
std::string officalHash = "MTY0MjY5ODgyMg==";
std::string officalSignature = "YkTPCGVj1Su+b4cqrOQPSwxHh79BTd9HZk/0OH71HjIbQ8/lSuKOEeNcpY9O7+4vgabIDRlyH5QmZmMV7X9s8eCk4cU7RAfn2YwE2pSvoik+upILLS9qmIDSaDz6LU2x";
const auto officalPublicKey = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEuFRsYw1MSBleBx0QnTbsvd8ygnTGiC9vnenBEthpHlQo8Iuu7n0vK/TqiI91nVMTzU8e0Uhi8dWiT2lSAkKxFnAsxeVzvH3rOSBCuKOo8A4T+Q5p+cRaiwzmCq4sdNzv";
struct BIOFreeAll { void operator()(BIO* p) { BIO_free_all(p); } };
}
std::string base64_decode(std::string const& encoded_string);
void tryOne(const std::vector<uint8_t>& binaryPublicKey);
void tryTwo(const std::vector<uint8_t>& binaryPublicKey);
void tryThree(const std::vector<uint8_t>& binaryPublicKey);
int main()
{
SSL_library_init();
SSL_load_error_strings();
const auto hashBytes = base64_decode(officalHash);
const auto publicKeyTemp = officalPublicKey;
std::unique_ptr<BIO, BIOFreeAll> b64(BIO_new(BIO_f_base64()));
BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
BIO* source = BIO_new_mem_buf(officalPublicKey, -1); // read-only source
BIO_push(b64.get(), source);
const int maxlen = strlen(officalPublicKey) / 4 * 3 + 1;
std::vector<uint8_t> decoded(maxlen);
const int len = BIO_read(b64.get(), decoded.data(), maxlen);
decoded.resize(len);
tryOne(decoded);
tryTwo(decoded);
tryThree(decoded);
return 0;
}
void tryOne(const std::vector<uint8_t>& binaryPublicKey)
{
EVP_PKEY* key;
EC_KEY* ec_key = EC_KEY_new_by_curve_name(NID_secp384r1);
EVP_PKEY* ret = EVP_PKEY_new();
EVP_PKEY_assign_EC_KEY(ret, ec_key);
unsigned char const* ptr = binaryPublicKey.data();
key = d2i_PublicKey(EVP_PKEY_EC, &ret, &ptr, binaryPublicKey.size());
if (!key) {
std::string error4 = ERR_error_string(ERR_get_error(), nullptr); //<- Error: error:10067066:elliptic curve routines:ec_GFp_simple_oct2point:invalid encoding
return;
}
}
void tryTwo(const std::vector<uint8_t>& binaryPublicKey)
{
const auto tempstr = base64_decode(officalSignature);
std::vector<uint8_t> SignatureBytes(tempstr.begin(), tempstr.end());
std::shared_ptr<EVP_MD_CTX> mdctx = std::shared_ptr<EVP_MD_CTX>(EVP_MD_CTX_create(), EVP_MD_CTX_free);
const EVP_MD* md = EVP_get_digestbyname("SHA512");
if (nullptr == md)
{
return;
}
if (0 == EVP_VerifyInit_ex(mdctx.get(), md, NULL))
{
return;
}
if (0 == EVP_VerifyUpdate(mdctx.get(), binaryPublicKey.data(), binaryPublicKey.size()))
{
return;
}
std::shared_ptr<BIO> b644 = std::shared_ptr<BIO>(BIO_new(BIO_f_base64()), BIO_free);
BIO_set_flags(b644.get(), BIO_FLAGS_BASE64_NO_NL);
std::shared_ptr<BIO> bPubKey = std::shared_ptr<BIO>(BIO_new(BIO_s_mem()), BIO_free);
BIO_puts(bPubKey.get(), officalPublicKey);
BIO_push(b644.get(), bPubKey.get());
std::shared_ptr<EVP_PKEY> pubkey = std::shared_ptr<EVP_PKEY>(d2i_PUBKEY_bio(b644.get(), NULL), EVP_PKEY_free);
if (!pubkey) {
std::string error = ERR_error_string(ERR_get_error(), nullptr);
return;
}
auto b = EVP_VerifyFinal(mdctx.get(), SignatureBytes.data(), SignatureBytes.size(), pubkey.get());
std::string error = ERR_error_string(ERR_get_error(), nullptr); // <- Error: error:0D0680A8:asn1 encoding routines:asn1_check_tlen:wrong tag
}
void tryThree(const std::vector<uint8_t>& binaryPublicKey)
{
EC_KEY* eckey = NULL;
std::vector<uint8_t> pubKeyVC = binaryPublicKey;
const unsigned char* pubKeyVCp = pubKeyVC.data();
const unsigned char** pubKeyVCpp = &pubKeyVCp;
eckey = EC_KEY_new_by_curve_name(NID_secp384r1);
EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
eckey = o2i_ECPublicKey(&eckey, pubKeyVCpp, pubKeyVC.size());
if (!EC_KEY_check_key(eckey)) {
std::string error = ERR_error_string(ERR_get_error(), nullptr); //<- Error: "error:0D07803A:asn1 encoding routines:asn1_item_embed_d2i:nested asn1 error"
}
}
std::string base64_decode(std::string const& encoded_string) {
int in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
char_array_4[i++] = encoded_string[in_]; in_++;
if (i == 4) {
for (i = 0; i < 4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}
if (i) {
for (j = i; j < 4; j++)
char_array_4[j] = 0;
for (j = 0; j < 4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}
return ret;
}
密钥生成: public 密钥是在 javascript:
中使用 SubtleCrypto 生成的function generateEcdsaKeypair() {
let keypair = window.crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-384"
},
true,
["sign", "verify"]
)
return keypair
}
然后使用 RFC 5280 第 4.1 节中使用 ASN.1 表示法定义的 SubjectPublicKeyInfo 格式导出:
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
async function exportCryptoKey(key) {
const exported = await window.crypto.subtle.exportKey(
"spki",
key
);
const exportedAsString = ab2str(exported);
const exportedAsBase64 = window.btoa(exportedAsString);
console.log("PublicKey: ", exportedAsBase64);
}
为了签署数据,我使用了以下函数:
function signEcdsa(privateKey, data) {
let signature = window.crypto.subtle.sign(
{
name: 'ECDSA',
hash: { name: 'SHA-512' },
},
privateKey,
data
)
return signature
}
但我什至无法将 base64 字符串转换为 public 密钥。验证更不用说了
整代档案:
js.html 文件:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Programiz</title>
</head>
<body>
<script src="js.js"></script>
</body>
</html>
js.js 文件: 可以在控制台选项卡下的检查 window 中找到输出。
function generateEcdsaKeypair() {
let keypair = window.crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-384"
},
true,
["sign", "verify"]
)
return keypair
}
// Generates a signature of the data given
function signEcdsa(privateKey, data) {
let signature = window.crypto.subtle.sign(
{
name: 'ECDSA',
hash: { name: 'SHA-512' },
},
privateKey,
data
)
return signature
}
function verifyEcdsa(publicKey, data, signature) {
let result = window.crypto.subtle.verify(
{
name: 'ECDSA',
hash: { name: 'SHA-512' },
},
publicKey,
signature,
data
)
return result
}
function _arrayBufferToBase64( buffer ) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa( binary );
}
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
function toHexString(byteArray) {
return Array.from(byteArray, function(byte) {
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('')
}
function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
return String.fromCharCode('0x' + p1);
}));
}
function b64DecodeUnicode(str) {
return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
function addNewLines(str) {
var finalString = '';
for(var i=0; i < str.length; i++) {
finalString += str.substring(0, 64) + '\n';
str = str.substring(64);
}
finalString += str;
return finalString;
}
function removeLines(pem) {
var lines = pem.split('\n');
var encodedString = '';
for(var i=0; i < lines.length; i++) {
encodedString += lines[i].trim();
}
return encodedString;
}
function stringToArrayBuffer(byteString){
var byteArray = new Uint8Array(byteString.length);
for(var i=0; i < byteString.length; i++) {
byteArray[i] = byteString.codePointAt(i);
}
return byteArray;
}
function arrayBufferToString(exportedPrivateKey){
var byteArray = new Uint8Array(exportedPrivateKey);
var byteString = '';
for(var i=0; i < byteArray.byteLength; i++) {
byteString += String.fromCodePoint(byteArray[i]);
}
return byteString;
}
async function exportCryptoKey(key) {
const exported = await window.crypto.subtle.exportKey(
"spki",
key
);
const exportedAsString = ab2str(exported);
const exportedAsBase64 = window.btoa(exportedAsString);
console.log("PublicKey: ", exportedAsBase64);
console.log(toHexString(exported));
var privateKeyDer = arrayBufferToString(exported); //pkcs#8 to DER
var privateKeyB64 = b64EncodeUnicode(privateKeyDer); //btoa(privateKeyDer);
var privateKeyPEMwithLines = addNewLines(privateKeyB64); //split PEM into 64 character strings
var privateKeyPEMwithoutLines = removeLines(privateKeyPEMwithLines); //join PEM
var privateKeyDerDecoded = b64DecodeUnicode(privateKeyPEMwithoutLines); // atob(privateKeyB64);
var privateKeyArrayBuffer = stringToArrayBuffer(privateKeyDerDecoded); //DER to arrayBuffer
console.log(exported);
console.log(privateKeyDer);
console.log(privateKeyB64);
console.log(privateKeyPEMwithLines);
console.log(privateKeyPEMwithoutLines);
console.log(privateKeyDerDecoded);
console.log(privateKeyArrayBuffer);
/*const pemExported = `-----BEGIN PUBLIC KEY-----\n${exportedAsBase64}\n-----END PUBLIC KEY-----`;
const exportKeyOutput = document.querySelector(".exported-key");
exportKeyOutput.textContent = pemExported;*/
}
function arrayBufferToString(exportedPrivateKey){
var byteArray = new Uint8Array(exportedPrivateKey);
var byteString = '';
for(var i=0; i < byteArray.byteLength; i++) {
byteString += String.fromCodePoint(byteArray[i]);
}
return byteString;
}
async function testSignEcdsa() {
let {
privateKey: aPrivate,
publicKey: aPublic
} = await generateEcdsaKeypair()
// A sends and signs data for B using their own private key
let data = window.crypto.getRandomValues(new Uint8Array(64))
let signature = await signEcdsa(aPrivate, data)
// B verifies A's signature using A's public key
let result = await verifyEcdsa(aPublic, data, signature)
let base64result = _arrayBufferToBase64(signature)
let signatureBase64 = window.btoa(String.fromCharCode.apply(null, new Uint8Array(signature)))
let dataBase64 = window.btoa(String.fromCharCode.apply(null, new Uint8Array(data)))
// Tests
// Signature is verified
console.log('Data signed/verified successfully: ', result)
console.log('Signature: ', signatureBase64)
console.log('Data: ', dataBase64)
exportCryptoKey(aPublic);
}
testSignEcdsa()
更新:
我把它放在这里是为了让其他人不需要通过 openssl 的文档来了解如何将 r|s 签名转换为 ans1 der 编码。不要忘记检查错误代码:
std::vector<uint8_t> Vault::convertP1363EncodingSignatureToASN1Der(const std::string& signatureInHexFormat) const
{ std::unique_ptr< ECDSA_SIG, std::function<void(ECDSA_SIG*)>> zSignature(ECDSA_SIG_new(), [](ECDSA_SIG* b) { ECDSA_SIG_free(b); });
std::unique_ptr< BIGNUM, std::function<void(BIGNUM*)>> r(nullptr, [](BIGNUM* b) { BN_free(b); });
BIGNUM* r_ptr = r.get();
std::unique_ptr< BIGNUM, std::function<void(BIGNUM*)>> s(nullptr, [](BIGNUM* b) { BN_free(b); });
BIGNUM* s_ptr = s.get();
const std::string sSignatureR = signatureInHexFormat.substr(0, signatureInHexFormat.size() / 2);
const std::string sSignatureS = signatureInHexFormat.substr(signatureInHexFormat.size() / 2);
BN_hex2bn(&r_ptr, sSignatureR.c_str());
BN_hex2bn(&s_ptr, sSignatureS.c_str());
ECDSA_SIG_set0(zSignature.get(), r_ptr, s_ptr);
unsigned char buffer[256];
unsigned char* pbuffer = buffer;
int signatureLength = i2d_ECDSA_SIG(zSignature.get(), nullptr);
signatureLength = i2d_ECDSA_SIG(zSignature.get(), &pbuffer);
return { buffer, buffer + signatureLength };
}
tryTwo()
允许通过以下更改成功验证发布的数据:
除了密钥和签名外,消息本身也需要验证。但是,在当前代码中根本没有使用该消息。必须在
VerifyUpdate()
中指定(而不是 public 键):void tryTwo(const std::vector<uint8_t>& binaryPublicKey) { const auto hashBytes = base64_decode(officalHash); ... if (0 == EVP_VerifyUpdate(mdctx.get(), hashBytes.c_str(), hashBytes.size())) { ...
顺便说一句,
officalHash
和hashBytes
是误导性的名称,因为应用了 未散列 消息。WebCrypto 生成 IEEE P1363 (r|s) 格式的签名,而
EVP_VerifyFinal()
需要 ASN.1/DER 格式的签名。发布的 ASN.1/DER 格式签名为(Base64 编码):std::string officalSignature = "MGUCMGJEzwhlY9Urvm+HKqzkD0sMR4e/QU3fR2ZP9Dh+9R4yG0PP5UrijhHjXKWPTu/uLwIxAIGmyA0Zch+UJmZjFe1/bPHgpOHFO0QH59mMBNqUr6IpPrqSCy0vapiA0mg8+i1NsQ==";
解释了两种格式之间的关系,例如here.
经过这两个改动,在我的机器上验证成功。