在iOS上使用Crypto ++验证消息时出错

时间:2016-03-24 20:52:47

标签: ios security rsa public-key crypto++

问题

我正在尝试使用其签名和公钥验证给定邮件。它使用iOS提供的安全框架工作正常,但我无法使用Crypto ++库(必须使用)使其工作。

我使用CryptoPP库执行了相同的步骤,并对所有内容进行了10次验证,重写了一些不同的部分,但它仍然引发了相同的异常:

  

“PK_Signer:此签名方案的密钥太短”

上下文

数据与

一起使用
  • 我收到一个带有标题,有效负载和签名的JWT(Json Web Token)。
  • 我检索服务的base64编码的X509证书(包括公钥)。

验证步骤

  1. 证书

    1. Base64解码证书
    2. 从证书中提取公钥
  2. 签名(JWB的第三段)

    1. 使用“=”
    2. 将签名填充为4的倍数
    3. URLBase64解码
  3. 要验证的消息

    1. 消息=(JSW标头)+“。” +(JWT Payload)。这已在代码中完成,消息是名为“headerAndPayload。
    2. 的参数
  4. 使用PKCS1,RSA

    验证SHA256字节
    1. 消息的SHA256摘要
    2. 使用以下方式验证:
      1. 公钥
      2. SHA256消息摘要
      3. 签名
  5. iOS工作代码

    (只有部分重要,因为验证在iOS上运行正常)

    • 证书

      1. NSData *certificateData = [[NSData alloc] initWithBase64EncodedString:certificateString options:0];

      2. 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库的代码(使用相同的数据集)

    我使用与描述相对应的数字和一些额外的注释复制/粘贴整个代码,例如返回的结构大小。

    +(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库复制了完全相同的概念。

    非常感谢任何帮助,谢谢。

1 个答案:

答案 0 :(得分:0)

  

vf.Put(摘要,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_SignerRSASSA_PKCS1v15_SHA_Verifier

同时结帐RSASS class,即RSA签名方案。我猜你可能想要一个RSASS<PKCS1v15, SHA256>::SignerRSASS<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;