如何解决C ++ OpenSSL麻烦在jwt.io上验证jwt签名
我在openssl上写了一些简单的代码,然后尝试在ECDSA SHA-256(EC KEY)上创建带有签名的有效jwt令牌。
此代码创建jwt令牌,并且方法验证签名返回true,但比我在jwt.io上检查jwt时,此服务返回无效状态。在我的变体签名和jwt.io上的变体中,不相等,但有效载荷和报头相等。
我认为此字符编码可能有些麻烦,但我不了解如何解决此问题。 PS标准c ++ 17.Visual Studio2019。来自nuget vc142的Openssl。
Main.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);
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);
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);
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,md,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,evp_verify_key);
if (ret != 1) {
//ERR_print_errors_fp(stderr);
return -1;
}
ret = EVP_DigestVerifyUpdate(mdctx,msg.size());
if (ret != 1) {
//ERR_print_errors_fp(stderr);
return -1;
}
ret = EVP_DigestVerifyFinal(mdctx,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;
}
priv.pem
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIGL5QyTmIkUuNdOHimdEYMvsHr35o4o/NPRZwwbN8OtBoAoGCCqGSM49
AwEHoUQDQgAEuUYhDTwZHZRM+thjsiaF8pOpcbcItiVQzQ25xlUVpJdqn73e290q
wH71poC8ArQZ5GuqKFO0eG3UONmLwImG/Q==
-----END EC PRIVATE KEY-----
public.pem
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuUYhDTwZHZRM+thjsiaF8pOpcbcI
tiVQzQ25xlUVpJdqn73e290qwH71poC8ArQZ5GuqKFO0eG3UONmLwImG/Q==
-----END PUBLIC KEY-----
解决方法
我怎么认为,这个问题来自签名编码。 Standart签名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,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_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;
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。