[英]C++ openssl trouble with verify jwt signature on jwt.io
我在 openssl 上写了一些简单的代码,我尝试创建有效的 jwt 令牌,并在 ECDSA SHA-256(EC KEY)上签名。
此代码创建 jwt 令牌,方法验证签名返回 true,但比我在 jwt.io 上检查 jwt 此服务返回无效状态。 在我的变体签名和 jwt.io 上的变体不相等,但有效载荷和标头相等。
我想这个字符编码可能有些麻烦,但我不明白如何解决这个问题。 PS 标准 c++ 17.Visual Studio 2019. Openssl 来自 nuget vc142.
主.cpp
#include <iostream>
#include <sstream>
#include <string>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/ecdsa.h>
#include <openssl/ec.h>
#include <openssl/conf.h>
#include <openssl/rand.h>
#include <openssl/pem.h>
#include <openssl/sha.h>
#include <openssl/md5.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
# if defined(_MSC_VER) && _MSC_VER >= 1900
#include <openssl/applink.c>
#endif
const char base64_url_alphabet[] =
{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
};
std::string ByteArrayToString(const uint8_t* arr, int size) {
std::ostringstream convert;
for (int a = 0; a < size; a++) {
convert << arr[a];
}
return convert.str();
}
class ecc_base {
public:
ecc_base() {
evp_sign_key = nullptr;
evp_verify_key = nullptr;
signature_len = sizeof(signature);
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
OPENSSL_config(NULL);
RAND_poll();
}
~ecc_base() {
if (evp_sign_key)
EVP_PKEY_free(evp_sign_key);
if (evp_verify_key)
EVP_PKEY_free(evp_verify_key);
EVP_cleanup();
CRYPTO_cleanup_all_ex_data();
ERR_free_strings();
}
std::string base64_url_encode(const std::string& in) {
std::string out;
int val = 0, valb = -6;
size_t len = in.length();
unsigned int i = 0;
for (i = 0; i < len; i++) {
unsigned char c = in[i];
val = (val << 8) + c;
valb += 8;
while (valb >= 0) {
out.push_back(base64_url_alphabet[(val >> valb) & 0x3F]);
valb -= 6;
}
}
if (valb > -6) {
out.push_back(base64_url_alphabet[((val << 8) >> (valb + 8)) & 0x3F]);
}
return out;
}
std::string base64_url_decode(const std::string& in) {
std::string out;
std::vector<int> T(256, -1);
unsigned int i;
for (i = 0; i < 64; i++) T[base64_url_alphabet[i]] = i;
int val = 0, valb = -8;
for (i = 0; i < in.length(); i++) {
unsigned char c = in[i];
if (T[c] == -1) break;
val = (val << 6) + T[c];
valb += 6;
if (valb >= 0) {
out.push_back(char((val >> valb) & 0xFF));
valb -= 8;
}
}
return out;
}
// loads in the pubkey
int load_pubkey(std::string pubkey)
{
FILE* fp;
// load in the keys
fp = fopen(pubkey.c_str(), "r");
if (!fp) {
return -1;
}
publickey = PEM_read_EC_PUBKEY(fp, NULL, NULL, NULL);
if (!publickey) {
//ERR_print_errors_fp(stderr);
return -1;
}
evp_verify_key = EVP_PKEY_new();
int ret;
ret = EVP_PKEY_assign_EC_KEY(evp_verify_key, publickey);
if (ret != 1) {
//ERR_print_errors_fp(stderr);
return -1;
}
fclose(fp);
std::cout << "pubkey load ok" << std::endl;
return 0;
}
int load_privkey(std::string privkey)
{
FILE* fp;
fp = fopen(privkey.c_str(), "r");
if (!fp) {
return -1;
}
privatekey = PEM_read_ECPrivateKey(fp, NULL, NULL, NULL);
if (!privatekey) {
//ERR_print_errors_fp(stderr);
return -1;
}
// validate the key
EC_KEY_check_key(privatekey);
evp_sign_key = EVP_PKEY_new();
int ret;
ret = EVP_PKEY_assign_EC_KEY(evp_sign_key, privatekey);
if (ret != 1) {
//ERR_print_errors_fp(stderr);
return -1;
}
fclose(fp);
std::cout << "privkey load ok" << std::endl;
return 0;
}
int generate_keys(std::string pubkeyfile, std::string privkeyfile, std::string curve_name)
{
EC_KEY* keygen;
int nid = to_nid(curve_name);
if (nid == -1) {
return -1;
}
// get curve name
keygen = EC_KEY_new_by_curve_name(nid);
if (!keygen) {
//ERR_print_errors_fp(stderr);
return -1;
}
int ret;
// run the key generation .. we aren't doing the curve parameters
ret = EC_KEY_generate_key(keygen);
if (ret != 1) {
//ERR_print_errors_fp(stderr);
return -1;
}
ret = EC_KEY_check_key(keygen);
if (ret != 1) {
//ERR_print_errors_fp(stderr);
return -1;
}
// wirte the keys
FILE* fp;
fp = fopen(pubkeyfile.c_str(), "w");
if (!fp) {
return -1;
}
PEM_write_EC_PUBKEY(fp, keygen);
fclose(fp);
fp = fopen(privkeyfile.c_str(), "w");
if (!fp) {
return -1;
}
PEM_write_ECPrivateKey(fp, keygen, NULL, NULL, 0, NULL, NULL);
fclose(fp);
EC_KEY_free(keygen);
std::cout << "keygen success" << std::endl;
return 0;
}
int sign(std::string_view msg, std::string_view sha_alg)
{
if (!evp_sign_key || !privatekey) {
std::cerr << "invalid sign key or private key is not loaded" << std::endl;
return -1;
}
const EVP_MD* md;
// mark the sha alg to use
if (sha_alg == "sha256") {
md = EVP_sha256();
}
else if (sha_alg == "sha1") {
md = EVP_sha1();
}
else {
return -1;
}
int ret;
EVP_MD_CTX* mdctx = EVP_MD_CTX_create();
ret = EVP_DigestSignInit(mdctx, NULL, md, NULL, evp_sign_key);
if (ret != 1) {
//ERR_print_errors_fp(stderr);
return -1;
}
ret = EVP_DigestSignUpdate(mdctx, msg.data(), msg.size());
if (ret != 1) {
//ERR_print_errors_fp(stderr);
return -1;
}
if(EVP_DigestSignFinal(mdctx, signature, &signature_len))
{
//this->signature = ByteArrayToString(signature, signature_len);
}
else return -1;
EVP_MD_CTX_destroy(mdctx);
std::cout << "signature generated : " << signature_len << " bytes" << std::endl;
return 0;
}
uint8_t* get_signature()
{
return signature;
}
std::string getSignature()
{
return ByteArrayToString(signature,signature_len);
}
size_t get_signature_len()
{
return signature_len;
}
int verify(std::string_view msg, uint8_t* signature, size_t signature_len, std::string sha_alg)
{
if (!msg.data() || !signature) {
std::cerr << "invalid msg or signature" << std::endl;
return -1;
}
const EVP_MD* md;
if (sha_alg == "sha256") {
md = EVP_sha256();
}
else if (sha_alg == "sha1") {
md = EVP_sha1();
}
else {
return -1;
}
int ret;
EVP_MD_CTX* mdctx = EVP_MD_CTX_create();
ret = EVP_DigestVerifyInit(mdctx, NULL, md, NULL, evp_verify_key);
if (ret != 1) {
//ERR_print_errors_fp(stderr);
return -1;
}
ret = EVP_DigestVerifyUpdate(mdctx, msg.data(), msg.size());
if (ret != 1) {
//ERR_print_errors_fp(stderr);
return -1;
}
ret = EVP_DigestVerifyFinal(mdctx, signature, signature_len);
if (ret != 1) {
//ERR_print_errors_fp(stderr);
return -1;
}
EVP_MD_CTX_destroy(mdctx);
std::cout << "verify ok" << std::endl;
return 0;
}
void dump_signature()
{
size_t i;
for (i = 0; i < signature_len; i++) {
if (i != 0) {
if (i % 16 == 0) {
printf("\n");
}
else {
printf("::");
}
}
printf("%02x", signature[i]);
}
printf("\n");
}
private:
int to_nid(std::string curvename)
{
if (curvename == "secp256k1") {
return NID_secp256k1;
}
else if (curvename == "brainpool256r1") {
return NID_brainpoolP256r1;
}
return -1;
}
//std::string signature;
uint8_t signature[256];
size_t signature_len;
EC_KEY* publickey;
EC_KEY* privatekey;
EVP_PKEY* evp_sign_key;
EVP_PKEY* evp_verify_key;
};
// validate the class implementation
//
int main(int argc, char** argv)
{
ecc_base ec;
int ret;
std::string header = R"({"alg":"ES256","typ":"JWT"})";
header = ec.base64_url_encode(header);
std::string payload = R"({"aud":"monitormaster-222706","exp":"1597742655","iat":"1597739055"})";
payload = ec.base64_url_encode(payload);
std::string signature;
std::string pkey = "priv.pem";
std::string pubkey = "public.pem";
std::string base64message = header + "." + payload;
ret = ec.load_pubkey(pubkey.c_str());
if (ret != 0) {
std::cerr << "privkey didn't load" << std::endl;
return -1;
}
ret = ec.load_privkey(pkey.c_str());
if (ret != 0) {
std::cerr << "privkey didn't load" << std::endl;
return -1;
}
ret = ec.sign(base64message, "sha256");
if (ret != 0) {
std::cerr << "failure to sign message" << std::endl;
return -1;
}
ret = ec.verify(base64message.data(),ec.get_signature(),ec.get_signature_len(),"sha256");
if (ret != 0) {
std::cerr << "failure to sign message" << std::endl;
return -1;
}
signature = ec.getSignature();
signature = ec.base64_url_encode(signature);
std::string jwt = header + "." + payload + "." + signature;
return 0;
}
私有文件
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIGL5QyTmIkUuNdOHimdEYMvsHr35o4o/NPRZwwbN8OtBoAoGCCqGSM49
AwEHoUQDQgAEuUYhDTwZHZRM+thjsiaF8pOpcbcItiVQzQ25xlUVpJdqn73e290q
wH71poC8ArQZ5GuqKFO0eG3UONmLwImG/Q==
-----END EC PRIVATE KEY-----
公共.pem
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuUYhDTwZHZRM+thjsiaF8pOpcbcI
tiVQzQ25xlUVpJdqn73e290qwH71poC8ArQZ5GuqKFO0eG3UONmLwImG/Q==
-----END PUBLIC KEY-----
我怎么想的,这个问题来自签名编码。 标准签名 openssl 库方法提供 ASN1 编码。 要像 jwt 令牌中的原始数据一样使用此签名,需要将 openssl 签名从 ASN1 格式转换为 DER 格式。 但为了验证,我们必须从 DER 恢复为 ASN1 格式。
int sign(std::string_view msg, std::string_view sha_alg)
{
if (!evp_sign_key || !privatekey) {
std::cerr << "invalid sign key or private key is not loaded" << std::endl;
return -1;
}
const EVP_MD* md;
// mark the sha alg to use
if (sha_alg == "sha256") {
md = EVP_sha256();
}
else if (sha_alg == "sha1") {
md = EVP_sha1();
}
else {
return -1;
}
int ret;
EVP_MD_CTX* mdctx = EVP_MD_CTX_create();
ret = EVP_DigestSignInit(mdctx, NULL, md, NULL, evp_sign_key);
if (ret != 1) {
//ERR_print_errors_fp(stderr);
return -1;
}
ret = EVP_DigestSignUpdate(mdctx, msg.data(), msg.size());
if (ret != 1) {
//ERR_print_errors_fp(stderr);
return -1;
}
if(EVP_DigestSignFinal(mdctx, signature, &signature_len))
{
//this->signature = ByteArrayToString(signature, signature_len);
}
else return -1;
///Convert from ASN1 to DER format
EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(evp_sign_key);
if (ec_key == NULL) return -1;
int degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec_key));
EC_KEY_free(ec_key);
/* Get the sig from the DER encoded version. */
ECDSA_SIG* ec_sig = ec_sig = d2i_ECDSA_SIG(NULL, signature, signature_len);
if (ec_sig == NULL) return -1;
const BIGNUM* ec_sig_r = NULL;
const BIGNUM* ec_sig_s = NULL;
ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s);
int r_len = BN_num_bytes(ec_sig_r);
int s_len = BN_num_bytes(ec_sig_s);
int bn_len = (degree + 7) / 8;
if ((r_len > bn_len) || (s_len > bn_len)) return -1;
/// Attention!!! std::vector<std::byte> from C++17, you can use unsigned char* but this C-style char's array need allocate zeros, how I member it's memset function. Or use std::vector<unsigned char*>.
std::vector<std::byte> raw_buf(static_cast<size_t>(bn_len) * 2);
BN_bn2bin(ec_sig_r, reinterpret_cast<unsigned char*>(raw_buf.data()) + bn_len - r_len);
BN_bn2bin(ec_sig_s, reinterpret_cast<unsigned char*>(raw_buf.data()) + raw_buf.size() - s_len);
std::string str(reinterpret_cast<char*>(raw_buf.data()), raw_buf.size());
str = base64_url_encode(str); /// This string correct raw-data signature for jwt token in base64URL coding.
///
EVP_MD_CTX_destroy(mdctx);
std::cout << "signature generated : " << signature_len << " bytes" << std::endl;
return 0;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.