在 iOS 上使用 Crypto++ 验证消息时出错
Error verifying message using Crypto++ on iOS
问题
我正在尝试使用签名和 public 密钥验证给定消息。它使用 iOS 提供的安全框架工作正常,但我无法使用 Crypto++ 库(必须使用)使其工作。
我使用 CryptoPP 库按照相同的步骤验证了所有内容 10 次,以不同的方式重写了一些部分,但它仍然抛出相同的异常:
"PK_Signer: key too short for this signature scheme"
上下文
使用的数据
- 我收到一个 JWT(Json Web 令牌),其中包含 header、负载和签名。
- 我检索了服务的 base64 编码 X509 证书(其中包括 public 密钥)。
验证所遵循的步骤
证书
- Base64解码证书
- 从证书中提取 public 密钥
签名(JWB 的第三部分)
- 用一些“=”将签名填充为4的倍数
- URLBase64 解码
消息验证
- 消息 = (JSW Header) + "." +(JWT 负载)。这已经在代码中完成,消息是名为“headerAndPayload.
的参数
使用 PKCS1、RSA 验证 SHA256 字节
- 消息的 SHA256 摘要
- 验证使用:
- Public键
- 消息的 SHA256 摘要
- 签名
iOS 工作代码
(只有重要的部分,因为验证在 iOS 上工作正常)
证书
NSData *certificateData = [[NSData alloc] initWithBase64EncodedString:certificateString options:0];
SecKeyRef getPublicKeyFromCertificate(certificateData)
网上找的,效果不错
使用 PKCS1、RSA 验证 SHA256 字节
BOOL PKCSVerifyBytesSHA256withRSA(NSData* message, NSData* signature, SecKeyRef publicKey)
{
size_t signedHashBytesSize = SecKeyGetBlockSize(publicKey);
const void* signedHashBytes = [signature bytes];
size_t hashBytesSize = CC_SHA256_DIGEST_LENGTH;
void* hashBytes = malloc(hashBytesSize);
if (!CC_SHA256([message bytes], (CC_LONG)[message length], hashBytes)) {
return NULL;
}
OSStatus status = SecKeyRawVerify(publicKey,
kSecPaddingPKCS1SHA256,
hashBytes,
hashBytesSize,
signedHashBytes,
signedHashBytesSize);
return status == errSecSuccess;
}
使用 CryptoPP 库的代码(使用同一组数据)
我 copy/paste 整个代码带有与描述相对应的数字和一些附加注释,例如返回的结构大小。
+(bool)verifyBase64EncodedCertificate:(NSString *)certificateString
base64URLEncodedJWTSignature:(NSString *)urlEncodedSignature
message:(NSString *)headerAndPayload
{
// 1. Certificate
// 1.1 Decode the certificate
std::string base64EncodedCertificate = certificateString.UTF8String;
std::string decodedCertificate;
CryptoPP::StringSource ss(base64EncodedCertificate,
true,
new CryptoPP::Base64Decoder(new CryptoPP::StringSink(decodedCertificate))
);
// 1.2 Extract Public Key from certificate
CryptoPP::ByteQueue certificateByteQueue, publicKeyByteQueue;
certificateByteQueue.Put((byte *)&decodedCertificate[0], decodedCertificate.size());
certificateByteQueue.MessageEnd();
try
{
GetPublicKeyFromCert(certificateByteQueue, publicKeyByteQueue);
// This method comes from CryptoPP docs so I assume it works... certificate gets checked again later on.
}
catch(std::exception &)
{
std::cerr << "Failed to extract the public key from the CA certificate." << std::endl;
return nil;
}
//publicKeyByteQueue.CurrentSize() = 294
// 2. Decode Signature
std::string base64URLEncodedSignature = urlEncodedSignature.UTF8String;
unsigned long paddingForURLEncodedSignature = 4 - (base64URLEncodedSignature.length() % 4);
base64URLEncodedSignature.insert(base64URLEncodedSignature.begin(), paddingForURLEncodedSignature, '=');
std::string decodedSignature;
CryptoPP::StringSource ss1(base64URLEncodedSignature,
true,
new CryptoPP::Base64URLDecoder(new CryptoPP::StringSink(decodedSignature))
);
const byte *decodedSignaturePointer = (byte *)&decodedSignature[0];
size_t decodedSignatureSize = decodedSignature.size();
// Certificate Signature as Byte Block
CryptoPP::SecByteBlock certSignature;
certSignature.Assign(decodedSignaturePointer, decodedSignatureSize);
// decodedSignatureSize = 256
// certSignature.size() = 256
// 3. Message to verify (available already concatenated)
std::string message = headerAndPayload.UTF8String;
const byte *messagePointer = (const byte *)message.c_str();
const size_t messageLength = message.length();
// MessageLength = 693
// 4.1 hash message using SHA256
byte digest [CryptoPP::SHA256::DIGESTSIZE];
CryptoPP::SHA256().CalculateDigest(digest, messagePointer, messageLength);
// 4.2 Create Verifier assigned public key and test
CryptoPP::AutoSeededRandomPool prng;
CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA256>::Verifier verifier;
verifier.AccessKey().Load(publicKeyByteQueue);
if (!verifier.AccessKey().Validate(prng, 3))
{
throw CryptoPP::Exception(CryptoPP::Exception::OTHER_ERROR, "Failed to validate public key");
}
// verifier.SignatureLength() = 256 = certSignature.size()
if(certSignature.size() != verifier.SignatureLength())
{
std::cerr << "The signature size is does not match the algorithm used for signing." << std::endl;
return 0;
}
// 4. Actual Verification (1st way of doing it)
CryptoPP::SignatureVerificationFilter vf(verifier);
try
{
vf.Put(digest, CryptoPP::SHA256::DIGESTSIZE);
vf.Put(certSignature, certSignature.size());
vf.MessageEnd(); // Throws exception here PK_Signer: key too short for this signature scheme
}
catch(std::exception &e)
{
std::cerr << "Caught an exception while verifying the signature:" << std::endl;
std::cerr << "\t" << e.what() << std::endl;
return 0;
}
if(vf.GetLastResult())
{
std::cout << "The signature verified." << std::endl;
}
else
{
std::cout << "Signature verification failed." << std::endl;
}
return 1;
// 4. Actual Verification (2d way of doing it)
bool verified = verifier.VerifyMessage(digest, CryptoPP::SHA256::DIGESTSIZE,
decodedSignaturePointer, decodedSignatureSize);
// Also throw same exception PK_Signer: key too short for this signature scheme
return verified;
我能看到纯 iOS 代码和 CryptoPP 代码之间的唯一区别是在验证过程中,iOS 方法需要一个额外的参数 kSecPaddingPKCS1SHA256
SecKeyRawVerify(publicKey,
kSecPaddingPKCS1SHA256,
...)
但除此之外,我觉得我已经使用 CryptoPP 库复制了完全相同的概念。
非常感谢任何帮助,谢谢。
vf.Put(digest, CryptoPP::SHA256::DIGESTSIZE);
签名是而不是哈希大小。签名是模数的大小(或更准确地说,[0,n-1]
)。协议成帧后,签名可能大于模数大小。另请参阅密码学堆栈交换中的 What is the length of an RSA signature?。
至于使用 "Raw Sign" 或 "Raw Encrypt" 创建等效的 iOS 示例,请参阅 Crypto++ wiki 上的 Raw RSA。做模幂运算等低级操作通常不是一个好主意。您应该尝试留在协议和密码系统中,例如 RSASSA_PKCS1v15_SHA_Signer
和 RSASSA_PKCS1v15_SHA_Verifier
.
同时检查 RSASS
class,它是 RSA 签名方案。我猜你可能想要 RSASS<PKCS1v15, SHA256>::Signer
和 RSASS<PKCS1v15, SHA256>::Verifier
:
$ grep -IR Signer * | grep typedef
luc.h:typedef LUCSS<PKCS1v15, SHA>::Signer LUCSSA_PKCS1v15_SHA_Signer;
pubkey.h: typedef PK_FinalTemplate<TF_SignerImpl<SchemeOptions> > Signer;
pubkey.h: typedef PK_FinalTemplate<DL_SignerImpl<SchemeOptions> > Signer;
rsa.h:typedef RSASS<PKCS1v15, SHA>::Signer RSASSA_PKCS1v15_SHA_Signer;
rsa.h:typedef RSASS<PKCS1v15, Weak1::MD2>::Signer RSASSA_PKCS1v15_MD2_Signer;
rsa.h:typedef RSASS<PKCS1v15, Weak1::MD5>::Signer RSASSA_PKCS1v15_MD5_Signer;
问题
我正在尝试使用签名和 public 密钥验证给定消息。它使用 iOS 提供的安全框架工作正常,但我无法使用 Crypto++ 库(必须使用)使其工作。
我使用 CryptoPP 库按照相同的步骤验证了所有内容 10 次,以不同的方式重写了一些部分,但它仍然抛出相同的异常:
"PK_Signer: key too short for this signature scheme"
上下文
使用的数据
- 我收到一个 JWT(Json Web 令牌),其中包含 header、负载和签名。
- 我检索了服务的 base64 编码 X509 证书(其中包括 public 密钥)。
验证所遵循的步骤
证书
- Base64解码证书
- 从证书中提取 public 密钥
签名(JWB 的第三部分)
- 用一些“=”将签名填充为4的倍数
- URLBase64 解码
消息验证
- 消息 = (JSW Header) + "." +(JWT 负载)。这已经在代码中完成,消息是名为“headerAndPayload. 的参数
使用 PKCS1、RSA 验证 SHA256 字节
- 消息的 SHA256 摘要
- 验证使用:
- Public键
- 消息的 SHA256 摘要
- 签名
iOS 工作代码
(只有重要的部分,因为验证在 iOS 上工作正常)
证书
NSData *certificateData = [[NSData alloc] initWithBase64EncodedString:certificateString options:0];
SecKeyRef getPublicKeyFromCertificate(certificateData)
网上找的,效果不错
使用 PKCS1、RSA 验证 SHA256 字节
BOOL PKCSVerifyBytesSHA256withRSA(NSData* message, NSData* signature, SecKeyRef publicKey) { size_t signedHashBytesSize = SecKeyGetBlockSize(publicKey); const void* signedHashBytes = [signature bytes]; size_t hashBytesSize = CC_SHA256_DIGEST_LENGTH; void* hashBytes = malloc(hashBytesSize); if (!CC_SHA256([message bytes], (CC_LONG)[message length], hashBytes)) { return NULL; } OSStatus status = SecKeyRawVerify(publicKey, kSecPaddingPKCS1SHA256, hashBytes, hashBytesSize, signedHashBytes, signedHashBytesSize); return status == errSecSuccess; }
使用 CryptoPP 库的代码(使用同一组数据)
我 copy/paste 整个代码带有与描述相对应的数字和一些附加注释,例如返回的结构大小。
+(bool)verifyBase64EncodedCertificate:(NSString *)certificateString
base64URLEncodedJWTSignature:(NSString *)urlEncodedSignature
message:(NSString *)headerAndPayload
{
// 1. Certificate
// 1.1 Decode the certificate
std::string base64EncodedCertificate = certificateString.UTF8String;
std::string decodedCertificate;
CryptoPP::StringSource ss(base64EncodedCertificate,
true,
new CryptoPP::Base64Decoder(new CryptoPP::StringSink(decodedCertificate))
);
// 1.2 Extract Public Key from certificate
CryptoPP::ByteQueue certificateByteQueue, publicKeyByteQueue;
certificateByteQueue.Put((byte *)&decodedCertificate[0], decodedCertificate.size());
certificateByteQueue.MessageEnd();
try
{
GetPublicKeyFromCert(certificateByteQueue, publicKeyByteQueue);
// This method comes from CryptoPP docs so I assume it works... certificate gets checked again later on.
}
catch(std::exception &)
{
std::cerr << "Failed to extract the public key from the CA certificate." << std::endl;
return nil;
}
//publicKeyByteQueue.CurrentSize() = 294
// 2. Decode Signature
std::string base64URLEncodedSignature = urlEncodedSignature.UTF8String;
unsigned long paddingForURLEncodedSignature = 4 - (base64URLEncodedSignature.length() % 4);
base64URLEncodedSignature.insert(base64URLEncodedSignature.begin(), paddingForURLEncodedSignature, '=');
std::string decodedSignature;
CryptoPP::StringSource ss1(base64URLEncodedSignature,
true,
new CryptoPP::Base64URLDecoder(new CryptoPP::StringSink(decodedSignature))
);
const byte *decodedSignaturePointer = (byte *)&decodedSignature[0];
size_t decodedSignatureSize = decodedSignature.size();
// Certificate Signature as Byte Block
CryptoPP::SecByteBlock certSignature;
certSignature.Assign(decodedSignaturePointer, decodedSignatureSize);
// decodedSignatureSize = 256
// certSignature.size() = 256
// 3. Message to verify (available already concatenated)
std::string message = headerAndPayload.UTF8String;
const byte *messagePointer = (const byte *)message.c_str();
const size_t messageLength = message.length();
// MessageLength = 693
// 4.1 hash message using SHA256
byte digest [CryptoPP::SHA256::DIGESTSIZE];
CryptoPP::SHA256().CalculateDigest(digest, messagePointer, messageLength);
// 4.2 Create Verifier assigned public key and test
CryptoPP::AutoSeededRandomPool prng;
CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA256>::Verifier verifier;
verifier.AccessKey().Load(publicKeyByteQueue);
if (!verifier.AccessKey().Validate(prng, 3))
{
throw CryptoPP::Exception(CryptoPP::Exception::OTHER_ERROR, "Failed to validate public key");
}
// verifier.SignatureLength() = 256 = certSignature.size()
if(certSignature.size() != verifier.SignatureLength())
{
std::cerr << "The signature size is does not match the algorithm used for signing." << std::endl;
return 0;
}
// 4. Actual Verification (1st way of doing it)
CryptoPP::SignatureVerificationFilter vf(verifier);
try
{
vf.Put(digest, CryptoPP::SHA256::DIGESTSIZE);
vf.Put(certSignature, certSignature.size());
vf.MessageEnd(); // Throws exception here PK_Signer: key too short for this signature scheme
}
catch(std::exception &e)
{
std::cerr << "Caught an exception while verifying the signature:" << std::endl;
std::cerr << "\t" << e.what() << std::endl;
return 0;
}
if(vf.GetLastResult())
{
std::cout << "The signature verified." << std::endl;
}
else
{
std::cout << "Signature verification failed." << std::endl;
}
return 1;
// 4. Actual Verification (2d way of doing it)
bool verified = verifier.VerifyMessage(digest, CryptoPP::SHA256::DIGESTSIZE,
decodedSignaturePointer, decodedSignatureSize);
// Also throw same exception PK_Signer: key too short for this signature scheme
return verified;
我能看到纯 iOS 代码和 CryptoPP 代码之间的唯一区别是在验证过程中,iOS 方法需要一个额外的参数 kSecPaddingPKCS1SHA256
SecKeyRawVerify(publicKey,
kSecPaddingPKCS1SHA256,
...)
但除此之外,我觉得我已经使用 CryptoPP 库复制了完全相同的概念。
非常感谢任何帮助,谢谢。
vf.Put(digest, CryptoPP::SHA256::DIGESTSIZE);
签名是而不是哈希大小。签名是模数的大小(或更准确地说,[0,n-1]
)。协议成帧后,签名可能大于模数大小。另请参阅密码学堆栈交换中的 What is the length of an RSA signature?。
至于使用 "Raw Sign" 或 "Raw Encrypt" 创建等效的 iOS 示例,请参阅 Crypto++ wiki 上的 Raw RSA。做模幂运算等低级操作通常不是一个好主意。您应该尝试留在协议和密码系统中,例如 RSASSA_PKCS1v15_SHA_Signer
和 RSASSA_PKCS1v15_SHA_Verifier
.
同时检查 RSASS
class,它是 RSA 签名方案。我猜你可能想要 RSASS<PKCS1v15, SHA256>::Signer
和 RSASS<PKCS1v15, SHA256>::Verifier
:
$ grep -IR Signer * | grep typedef
luc.h:typedef LUCSS<PKCS1v15, SHA>::Signer LUCSSA_PKCS1v15_SHA_Signer;
pubkey.h: typedef PK_FinalTemplate<TF_SignerImpl<SchemeOptions> > Signer;
pubkey.h: typedef PK_FinalTemplate<DL_SignerImpl<SchemeOptions> > Signer;
rsa.h:typedef RSASS<PKCS1v15, SHA>::Signer RSASSA_PKCS1v15_SHA_Signer;
rsa.h:typedef RSASS<PKCS1v15, Weak1::MD2>::Signer RSASSA_PKCS1v15_MD2_Signer;
rsa.h:typedef RSASS<PKCS1v15, Weak1::MD5>::Signer RSASSA_PKCS1v15_MD5_Signer;