xcode - Xcode 12:函数“SecKeyEncrypt”的隐式声明在 C99 中无效
问题描述
升级到 Xcode 12.0.1 后,用于文件解密的命令行 Mac 应用程序(用 Swift 编写)在尝试构建时遇到以下错误:
- 函数“SecKeyEncrypt”的隐式声明在 C99 中无效
- 函数“SecKeyRawSign”的隐式声明在 C99 中无效
- 函数“SecKeyDecrypt”的隐式声明在 C99 中无效
en-/de-cryption 代码(用 Objective C 编写)取自https://github.com/ideawu/Objective-C-RSA - 它在 Xcode 11 中工作得很好。它使用这个 import 语句
#import <Security/Security.h>
<Security/Security.h> 有一行
#include <Security/SecKey.h>
在这个文件中,声明了方法:
#if SEC_OS_IPHONE
/*!
@function SecKeyRawSign
@abstract Given a private key and data to sign, generate a digital
signature.
@param key Private key with which to sign.
@param padding See Padding Types above, typically kSecPaddingPKCS1SHA1.
@param dataToSign The data to be signed, typically the digest of the
actual data.
@param dataToSignLen Length of dataToSign in bytes.
@param sig Pointer to buffer in which the signature will be returned.
@param sigLen IN/OUT maximum length of sig buffer on input, actualy
length of sig on output.
@result A result code. See "Security Error Codes" (SecBase.h).
@discussion If the padding argument is kSecPaddingPKCS1, PKCS1 padding
will be performed prior to signing. If this argument is kSecPaddingNone,
the incoming data will be signed "as is".
When PKCS1 padding is performed, the maximum length of data that can
be signed is the value returned by SecKeyGetBlockSize() - 11.
NOTE: The behavior this function with kSecPaddingNone is undefined if the
first byte of dataToSign is zero; there is no way to verify leading zeroes
as they are discarded during the calculation.
If you want to generate a proper PKCS1 style signature with DER encoding
of the digest type - and the dataToSign is a SHA1 digest - use
kSecPaddingPKCS1SHA1.
*/
OSStatus SecKeyRawSign(
SecKeyRef key,
SecPadding padding,
const uint8_t *dataToSign,
size_t dataToSignLen,
uint8_t *sig,
size_t *sigLen)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_2_0);
/*!
@function SecKeyRawVerify
@abstract Given a public key, data which has been signed, and a signature,
verify the signature.
@param key Public key with which to verify the signature.
@param padding See Padding Types above, typically kSecPaddingPKCS1SHA1.
@param signedData The data over which sig is being verified, typically
the digest of the actual data.
@param signedDataLen Length of signedData in bytes.
@param sig Pointer to the signature to verify.
@param sigLen Length of sig in bytes.
@result A result code. See "Security Error Codes" (SecBase.h).
@discussion If the padding argument is kSecPaddingPKCS1, PKCS1 padding
will be checked during verification. If this argument is kSecPaddingNone,
the incoming data will be compared directly to sig.
If you are verifying a proper PKCS1-style signature, with DER encoding
of the digest type - and the signedData is a SHA1 digest - use
kSecPaddingPKCS1SHA1.
*/
OSStatus SecKeyRawVerify(
SecKeyRef key,
SecPadding padding,
const uint8_t *signedData,
size_t signedDataLen,
const uint8_t *sig,
size_t sigLen)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_2_0);
/*!
@function SecKeyEncrypt
@abstract Encrypt a block of plaintext.
@param key Public key with which to encrypt the data.
@param padding See Padding Types above, typically kSecPaddingPKCS1.
@param plainText The data to encrypt.
@param plainTextLen Length of plainText in bytes, this must be less
or equal to the value returned by SecKeyGetBlockSize().
@param cipherText Pointer to the output buffer.
@param cipherTextLen On input, specifies how much space is available at
cipherText; on return, it is the actual number of cipherText bytes written.
@result A result code. See "Security Error Codes" (SecBase.h).
@discussion If the padding argument is kSecPaddingPKCS1 or kSecPaddingOAEP,
PKCS1 (respectively kSecPaddingOAEP) padding will be performed prior to encryption.
If this argument is kSecPaddingNone, the incoming data will be encrypted "as is".
kSecPaddingOAEP is the recommended value. Other value are not recommended
for security reason (Padding attack or malleability).
When PKCS1 padding is performed, the maximum length of data that can
be encrypted is the value returned by SecKeyGetBlockSize() - 11.
When memory usage is a critical issue, note that the input buffer
(plainText) can be the same as the output buffer (cipherText).
*/
OSStatus SecKeyEncrypt(
SecKeyRef key,
SecPadding padding,
const uint8_t *plainText,
size_t plainTextLen,
uint8_t *cipherText,
size_t *cipherTextLen)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_2_0);
/*!
@function SecKeyDecrypt
@abstract Decrypt a block of ciphertext.
@param key Private key with which to decrypt the data.
@param padding See Padding Types above, typically kSecPaddingPKCS1.
@param cipherText The data to decrypt.
@param cipherTextLen Length of cipherText in bytes, this must be less
or equal to the value returned by SecKeyGetBlockSize().
@param plainText Pointer to the output buffer.
@param plainTextLen On input, specifies how much space is available at
plainText; on return, it is the actual number of plainText bytes written.
@result A result code. See "Security Error Codes" (SecBase.h).
@discussion If the padding argument is kSecPaddingPKCS1 or kSecPaddingOAEP,
the corresponding padding will be removed after decryption.
If this argument is kSecPaddingNone, the decrypted data will be returned "as is".
When memory usage is a critical issue, note that the input buffer
(plainText) can be the same as the output buffer (cipherText).
*/
OSStatus SecKeyDecrypt(
SecKeyRef key, /* Private key */
SecPadding padding, /* kSecPaddingNone,
kSecPaddingPKCS1,
kSecPaddingOAEP */
const uint8_t *cipherText,
size_t cipherTextLen, /* length of cipherText */
uint8_t *plainText,
size_t *plainTextLen) /* IN/OUT */
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_2_0);
#endif // SEC_OS_IPHONE
引发错误的解密方法如下所示:
+ (NSData *)decryptData:(NSData *)data withKeyRef:(SecKeyRef) keyRef{
const uint8_t *srcbuf = (const uint8_t *)[data bytes];
size_t srclen = (size_t)data.length;
size_t block_size = SecKeyGetBlockSize(keyRef) * sizeof(uint8_t);
UInt8 *outbuf = malloc(block_size);
size_t src_block_size = block_size;
NSMutableData *ret = [[NSMutableData alloc] init];
for(int idx=0; idx<srclen; idx+=src_block_size){
//NSLog(@"%d/%d block_size: %d", idx, (int)srclen, (int)block_size);
size_t data_len = srclen - idx;
if(data_len > src_block_size){
data_len = src_block_size;
}
size_t outlen = block_size;
OSStatus status = noErr;
status = SecKeyDecrypt(keyRef, // <<<<<<<<<<<<<<<<<<<<<< This raises the error
kSecPaddingNone,
srcbuf + idx,
data_len,
outbuf,
&outlen
);
if (status != 0) {
NSLog(@"SecKeyEncrypt fail. Error Code: %d", status);
ret = nil;
break;
}else{
//the actual decrypted data is in the middle, locate it!
int idxFirstZero = -1;
int idxNextZero = (int)outlen;
for ( int i = 0; i < outlen; i++ ) {
if ( outbuf[i] == 0 ) {
if ( idxFirstZero < 0 ) {
idxFirstZero = i;
} else {
idxNextZero = i;
break;
}
}
}
[ret appendBytes:&outbuf[idxFirstZero+1] length:idxNextZero-idxFirstZero-1];
}
}
free(outbuf);
CFRelease(keyRef);
return ret;
}
似乎不能再直接调用加密/解密函数了。我不确定这里发生了什么变化——问题是由于 Xcode 的变化引起的吗?更重要的是:如何解决这个问题?(我在 Catalina 10.15.6 上)非常感谢任何帮助!(如果缺少某些信息,请告诉我。)
解决方案
正如 Phillip Mills 指出的那样,#if SEC_OS_IPHONE
Security/SecKey.h 包含一个条件,实际上,如果我创建一个 iOS 应用程序而不是 Mac 应用程序,则可以毫无错误地构建该项目。所以我假设 Xcode 12 已经引入了条件,并且不能在 macOS 上调用 SecKeyEncrypt 和 SecKeyDecrypt 之类的函数(除非 Mac Catalyst)——可能从未得到官方支持。
无论如何,我现在已经通过 CocoaPods 添加了 OpenSSL 并让解密部分正常工作。如果您有兴趣,我的头文件 RSACryptoOpenSSL.h 如下所示:
#import <Foundation/Foundation.h>
#import <openssl/bio.h>
#import <openssl/pem.h>
NS_ASSUME_NONNULL_BEGIN
@interface RSACryptoOpenSSL : NSObject
+ (NSString *)decryptMacOsString:(NSString *)str privateKey:(NSString *)privKey;
@end
NS_ASSUME_NONNULL_END
和这样的实现文件
#import "RSACryptoOpenSSL.h"
@implementation RSACryptoOpenSSL
+ (NSString *)decryptMacOsString:(NSString *)str privateKey:(NSString *)privKey
{
NSData *data = [[NSData alloc] initWithBase64EncodedString:str options:NSDataBase64DecodingIgnoreUnknownCharacters];
// load private key
const char *private_key = [privKey UTF8String];
BIO *bio = BIO_new_mem_buf((void*)private_key, (int)strlen(private_key));
RSA *rsa_privatekey = PEM_read_bio_RSAPrivateKey(bio, NULL, 0, NULL);
BIO_free(bio);
int maxSize = RSA_size(rsa_privatekey);
unsigned char *output = (unsigned char *) malloc(maxSize * sizeof(char));
int bytes __unused = RSA_private_decrypt((int)[data length], [data bytes], output, rsa_privatekey, RSA_PKCS1_PADDING);
NSString *ret = [NSString stringWithUTF8String:(char *)output];
return ret;
}
@end
感谢doginthehat和timburks提供的宝贵信息。
推荐阅读
- android - 在 Android 6.0 上读取 Elf32 的 PT_DYNAMIC 条目时出现内存访问错误
- c# - C# 中类似 Azure 的 Powershell 登录
- excel - 如何创建一个函数来接受连接的源值 - Excel VBA
- c++ - 从指针引用 C++ 创建一个新对象
- excel - iferror 返回#Name?而不是字符串
- java - 为什么我不能在 Java 中调用我的 websocket 服务器之外的方法?
- tcp - 顶点 tcp 客户端丢弃数据包
- spring-boot - 即使正确注释,Spring Boot 也找不到 bean
- python - Matplotlib draw() 在 MacOS 上的 pyqt 环境中不起作用
- jquery - 用 jquery 和 javascript 按钮的值填充许多输入字段