繁体   English   中英

C++ openssl 验证 jwt jwt.io 上的签名有问题

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM