C ++ OpenSSL麻烦在jwt.io上验证jwt签名

如何解决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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-