C++;如何使用OpenSSL验证Google JWT(RS256) 我试图用OpenSSL和C++来验证JWT令牌。

C++;如何使用OpenSSL验证Google JWT(RS256) 我试图用OpenSSL和C++来验证JWT令牌。,c++,openssl,jwt,google-oauth,C++,Openssl,Jwt,Google Oauth,作为实验和学习的练习,请不要建议使用第三方库来完成这项工作 令牌具有通常的形式Header.Payload.Signature,我可以对其进行Base64URL解码,但无法验证签名 以下RFC未提及如何使用RS256进行处理: 根据JWS签名输入验证JWS签名 ASCII(BASE64URL(UTF8(JWS保护头))| |'。| | BASE64URL(JWS 有效负载),以为所使用的算法定义的方式 必须由“alg”(算法)的值准确表示 标题参数,必须存在 我的发言如下: 接收器如何检查RS2

作为实验和学习的练习,请不要建议使用第三方库来完成这项工作

令牌具有通常的形式
Header.Payload.Signature
,我可以对其进行Base64URL解码,但无法验证签名

以下RFC未提及如何使用RS256进行处理:

根据JWS签名输入验证JWS签名 ASCII(BASE64URL(UTF8(JWS保护头))| |'。| | BASE64URL(JWS 有效负载),以为所使用的算法定义的方式 必须由“alg”(算法)的值准确表示 标题参数,必须存在

我的发言如下:

接收器如何检查RS256签名?JWT的接受者 届时:

  • 获取标头和有效负载,并使用SHA-256散列所有内容
  • 使用公钥解密签名,并获取签名哈希
  • 接收者将签名散列与他自己根据报头和有效载荷计算的散列进行比较
这两个哈希匹配吗?这证明了JWT确实是由身份验证服务器创建的

当使用Base64Url解码标头时,我得到了一个有效的JSON。有效负载也是有效的:

{"alg":"RS256","kid":"03b2d22c2fecf873ed19e5b8cf704afb7e2ed4be","typ":"JWT"}
然后,我为给定的孩子找回了正确的证书

我的测试代码是:

// Split fields for convenience
static std::string GTOKEN_B64URL_HEADER ("eyJhb...shortened...V1QifQ");
static std::string GTOKEN_B64URL_PAYLOAD("eyJpc...shortened...MzExfQ");
static std::string GTOKEN_B64URL_SIGN   ("k7Ppq...shortened...TJCTdQ");

// From https://www.googleapis.com/oauth2/v1/certs using the specified "kid"
static const char* CERT =
        "-----BEGIN CERTIFICATE-----\n"
        "MIIDJjCCAg6gAwIBAgIIHdBXKdu8rS4wDQYJKoZIhvcNAQEFBQAwNjE0MDIGA1UE\n"
        ...
        "MB7mbimIU22061HCjFbdlEscy26X/BXtxPpQjEwbkzJ5wy2bVu2AIIdo\n"
        "-----END CERTIFICATE-----\n";

// Preparation: Get the public key from the PEM cert
//
BIO *memCert = BIO_new_mem_buf(CERT, -1);
X509* cert= PEM_read_bio_X509(memCert, nullptr, nullptr, nullptr);
if (nullptr == cert) {
    showOpenSSLErrors("Unable to load CERT: ");
    return;
}

EVP_PKEY* key = X509_get_pubkey(cert);
if (nullptr == key) {
    showOpenSSLErrors("Unable to get pubkey from cert: ");
    return;
}

int idKey = EVP_PKEY_id(key);
int type = EVP_PKEY_type(idKey);
if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA2) {
    std::cout << "Key type is not RSA" << std::endl;
    return;
}

RSA* rsa = EVP_PKEY_get1_RSA(key);
if (nullptr == rsa) {
    showOpenSSLErrors("Invalid RSA: ");
    return;
}

// 1) take the header and the payload, and hash everything with SHA-256
//
std::string whatToValidate;
computeHashSHA256(GTOKEN_B64URL_HEADER+"."+GTOKEN_B64URL_PAYLOAD, whatToValidate);

// 2) decrypt the signature using the public key ...
//
std::string signatureB64 = decodeBase64URL(GTOKEN_B64URL_SIGN);

std::string signature;
signature.resize( RSA_size(rsa) );
int len = RSA_public_decrypt(
    signatureB64.size(),
    (unsigned char*)signatureB64.data(), 
    (unsigned char*)signature.data(),
    rsa, RSA_NO_PADDING);
if (len == -1) {
    std::cout << "Decrypt failed" << std::endl;
    return;
}

signature.resize(len);

// 2) ... and obtain the signature hash
std::string signatureHash;
computeHashSHA256(signature, signatureHash);

if (whatToValidate.size() != signatureHash.size()) {
    printf("Len does not match! (%d vs %d) \n", whatToValidate.size(), signatureHash.size());
    return;
}

std::cout << "whatToValidate: " << whatToValidate << std::endl;
std::cout << "signatureHash:  " << signatureHash << std::endl;

// 3) the receiver compares the signature hash with the hash that he
//    calculated himself based on the Header and the Payload
if (signatureHash != whatToValidate) {
    printf("    comparison FAILED!!!\n");
}

// Extra check: Ensure SHA256 algorithm is working
//
const std::string decodedHeader(decodeBase64URL(GTOKEN_B64URL_HEADER));

std::string headerSHA256;
computeHashSHA256(decodedHeader, headerSHA256);

std::cout << "Header:         " << decodedHeader << std::endl;
std::cout << "Header SHA256:  " << headerSHA256 << std::endl;
std::cout << "Signature size: " << signature.size() << "(" << GTOKEN_B64URL_SIGN.size() << " base64Url)" << std::endl;
std::cout << "Validate:       " << whatToValidate.size() << std::endl;
std::cout << std::endl;
标头SHA256证明computeHashSHA256()按预期工作

我做错了什么

我有没有其他方法可以使用?(还尝试了RSA_verify(),但没有成功,因为我真的不知道如何进行)


编辑

JWS签名输入的SHA256将为32字节。whatToValidate(SHA256的ASCII表示)将为64字节。签名长度为256字节

签名看起来既不像原始签名也不像ASCII签名

因此产生了一个问题:JWS签名输入上的SHA256应该是什么


编辑-Base64URL解码签名(二进制):


编辑-解密的签名:

0x00 0x01 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x00 0x30 0x31 0x30
0x0d 0x06 0x09 0x60 0x86 0x48 0x01 0x65 0x03 0x04 0x02 0x01 0x05 0x00 0x04 0x20
0xd4 0x98 0x1a 0x11 0xb8 0xd9 0xa6 0x86 0xe7 0xf9 0x91 0x9c 0xf7 0xd6 0x47 0x7c
0x5e 0x7c 0x0e 0x35 0xfc 0xd6 0x11 0x33 0xad 0x2f 0xdb 0x8c 0xb8 0x45 0xb4 0x9a

您的主要问题是,您正在计算调用
RSA\u public\u decrypt
时恢复的散列的散列

RSA签名只是一些数据加上一些通过RSA算法运行的额外信息的散列。当您调用
RSA\u public\u decrypt
时,您将恢复的是原始哈希+元数据;不是最初签名的邮件。您将其视为已签名的原始消息,然后计算其哈希值以与JWT进行比较

您将遇到的第二个问题是,RSA签名在输入RSA算法之前,会被填充到密钥模的大小。这就是为什么
RSA\u public\u decrypt
的输出是256字节长的,尽管原始散列只有32字节长。实际恢复的哈希是输出的最后32个字节

综上所述,您需要做如下操作:

// 1) Calculate the SHA256 hash of the base64urled header and payload
std::string to_validate = sha256raw(TOKEN_B64URL_HEADER + "." + TOKEN_B64URL_PAYLOAD);

// 2) decrypt the signature
std::string raw_signature = decodeBase64URL(TOKEN_B64URL_SIGNATURE);

std::string decrypted_signature(RSA_size(rsa), '\0');
int len = RSA_public_decrypt(
    raw_signature.size(),
    reinterpret_cast<unsigned char*>(raw_signature.data()),
    reinterpret_cast<unsigned char*>(decrypted_signature.data()),
    rsa,
    RSA_PKCS1_PADDING // Will verify that the padding is at least structurally correct
);
decrypted_signature.resize(len);

// 3) Extract the last 32 bytes to get the original message hash from the decrypted signature
std::string recovered_hash = decrypted_signature.substr(decrypted_signature.size() - to_validate.size())


// 4) Compare the recovered hash to the hash of the token
if (to_validate == recovered_hash) {
    std::cout << "Signature verified\n";
} else {
    std::cout << "Signature validation failed\n"
}
// 1) Calculate the SHA256 hash of the base64urled header and payload
std::string to_validate = sha256raw(TOKEN_B64URL_HEADER + "." + TOKEN_B64URL_PAYLOAD);

// 2) verify the signature
std::string raw_signature = decodeBase64URL(TOKEN_B64URL_SIGNATURE);
int verified = RSA_verify(
    NID_sha256,
    reinterpret_cast<unsigned char*>(to_validate.data()),
    to_validate.size(),
    reinterpret_cast<unsigned char*>(raw_signature.data()),
    raw_signature.size(),
    rsa
);

if (verified) {
    std::cout << "Signature verified\n";
} else {
    std::cout << "Signature validation failed\n"
}

有关这两个版本的实时演示,请参阅。whatToValidate是您自己的哈希计算,这很好<代码>签名的结果是
RSA\u public\u decrypt
,对吧?!我认为签名是要比较的哈希值。但是还有
computehasha256(签名,signatureHash),所以再次对其进行哈希。为什么?我正在遵循链接的JWT指南。第二步是:“使用公钥(RSA_public_decrypt)解密签名,并获得签名哈希(解密签名时计算SHA256)”。好的,但只是为了测试,也许您可以打印签名的值并在此处显示。嗯,看起来,
RSA\u public\u decrypt
将实际输出PKCS#1编码的散列。无论如何,你应该使用RSA\u verify
;下面是如何使用该函数的详细说明。@jps-Sure;我正在写一个答案,其中也包括这一点。谢谢,了解了一些关于RSA的知识。现在看完这个,,似乎OP已经非常接近了:
whatToValidate:D4981A11AB8D9A686E7F9919CF7D6477C5E7C0E35FCD61133D2FDB8CB845B49A
与最后32个字节相比:
0xd4 0x98 0x1a 0x11 0xb8 0xd9 0xa6 0x86 0xe7 0xf9 0x91 0x9c 0xf7 0xd6 0xd6 0x47 0x7c 0x5e 0x35 0xfc 0xd6 0xd6 0x11 0x33 0xad 2F 0x8c 0xb8 0xB45,只需将结果转换成相同的形式并进行比较。
// 1) Calculate the SHA256 hash of the base64urled header and payload
std::string to_validate = sha256raw(TOKEN_B64URL_HEADER + "." + TOKEN_B64URL_PAYLOAD);

// 2) decrypt the signature
std::string raw_signature = decodeBase64URL(TOKEN_B64URL_SIGNATURE);

std::string decrypted_signature(RSA_size(rsa), '\0');
int len = RSA_public_decrypt(
    raw_signature.size(),
    reinterpret_cast<unsigned char*>(raw_signature.data()),
    reinterpret_cast<unsigned char*>(decrypted_signature.data()),
    rsa,
    RSA_PKCS1_PADDING // Will verify that the padding is at least structurally correct
);
decrypted_signature.resize(len);

// 3) Extract the last 32 bytes to get the original message hash from the decrypted signature
std::string recovered_hash = decrypted_signature.substr(decrypted_signature.size() - to_validate.size())


// 4) Compare the recovered hash to the hash of the token
if (to_validate == recovered_hash) {
    std::cout << "Signature verified\n";
} else {
    std::cout << "Signature validation failed\n"
}
// 1) Calculate the SHA256 hash of the base64urled header and payload
std::string to_validate = sha256raw(TOKEN_B64URL_HEADER + "." + TOKEN_B64URL_PAYLOAD);

// 2) verify the signature
std::string raw_signature = decodeBase64URL(TOKEN_B64URL_SIGNATURE);
int verified = RSA_verify(
    NID_sha256,
    reinterpret_cast<unsigned char*>(to_validate.data()),
    to_validate.size(),
    reinterpret_cast<unsigned char*>(raw_signature.data()),
    raw_signature.size(),
    rsa
);

if (verified) {
    std::cout << "Signature verified\n";
} else {
    std::cout << "Signature validation failed\n"
}