Why would SecKeyEncrypt return paramErr (-50) for input strings longer than 246 bytes? - ios

I am using SecKeyEncrypt with a JSON formatted string as input. If pass SecKeyEncrypt a plainTextLength of less than 246, it works. If I pass it a length of 246 or more, it fails with return value: paramErr (-50).
It could be a matter of the string itself. An example of what I might send SecKeyEncrypt is:
{"handle":"music-list","sym_key":"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALeaEO7ZrjgOFGLBzBHZtQuzH2GNDYMLWP+fIFNu5Y+59C6HECY+jt0yOXXom2mzp/WYYI/9G+Ig8OD6YiKv2nMCAwEAAQ==","app_id":"xgfdt.LibraryTestApp","api_key":"7e080f74de3625b90dd293fc8be560a5cdfafc08"}
The 245th character is '0'.
The ONLY input that is changing between this working and is the plainTextLength. SecKeyGetBlockSize() is returning 256 to me, so any input up to 256 characters long should work.
Here is my encrypt method:
+ (NSData*)encrypt:(NSString*)data usingPublicKeyWithTag:(NSString*)tag
{
OSStatus status = noErr;
size_t cipherBufferSize;
uint8_t *cipherBuffer;
// [cipherBufferSize]
size_t dataSize = 246;//[data lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
const uint8_t* textData = [[data dataUsingEncoding:NSUTF8StringEncoding] bytes];
SecKeyRef publicKey = [Encryption copyPublicKeyForTag:tag];
NSAssert(publicKey, #"The public key being referenced by tag must have been stored in the keychain before attempting to encrypt data using it!");
// Allocate a buffer
cipherBufferSize = SecKeyGetBlockSize(publicKey);
// this value will not get modified, whereas cipherBufferSize may.
const size_t fullCipherBufferSize = cipherBufferSize;
cipherBuffer = malloc(cipherBufferSize);
NSMutableData* accumulatedEncryptedData = [NSMutableData dataWithCapacity:0];
// Error handling
for (int ii = 0; ii*fullCipherBufferSize < dataSize; ii++) {
const uint8_t* dataToEncrypt = (textData+(ii*fullCipherBufferSize));
const size_t subsize = (((ii+1)*fullCipherBufferSize) > dataSize) ? fullCipherBufferSize-(((ii+1)*fullCipherBufferSize) - dataSize) : fullCipherBufferSize;
// Encrypt using the public key.
status = SecKeyEncrypt( publicKey,
kSecPaddingPKCS1,
dataToEncrypt,
subsize,
cipherBuffer,
&cipherBufferSize
);
[accumulatedEncryptedData appendBytes:cipherBuffer length:cipherBufferSize];
}
if (publicKey) CFRelease(publicKey);
free(cipherBuffer);
return accumulatedEncryptedData;
}

From the documentation:
plainTextLen
Length in bytes of the data in the plainText buffer. This must be less than or equal to the value returned by the SecKeyGetBlockSize function. When PKCS1 padding is performed, the maximum length of data that can be encrypted is 11 bytes less than the value returned by the SecKeyGetBlockSize function (secKeyGetBlockSize() - 11).
(emphasis mine)
You're using PKCS1 padding. So if the block size is 256, you can only encrypt up to 245 bytes at a time.

Related

How to decrypt AES 256 https://aesencryption.net/ - IOS

Plain text : Encrypt and decrypt text with AES algorithm
Key (256) : testsecret
Result (https://aesencryption.net/) : iFhSyFY3yYoO2G6GVGkdhZJjD+h0Pxv5fQnO3xIarzuGQSkIxlrpSprC5bC3gJ2U
i use small code in object to decrypt this this text :
(NSData*)AES256DecryptWithKey:(NSString*)key {
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256 + 1]; // room for terminator (unused)
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
// fetch key data
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [self length];
//See the doc: For block ciphers, the output size will always be less than or
//equal to the input size plus the size of one block.
//That's why we need to add the size of one block here
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void* buffer = malloc(bufferSize);
size_t numBytesDecrypted = 0; // char iv[kCCBlockSizeAES128 + 1]; bzero(iv, sizeof(iv)) ;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding ,
keyPtr, kCCKeySizeAES256,
NULL /* initialization vector (optional) /,
[self bytes], dataLength, / input /
buffer, bufferSize, / output */
&numBytesDecrypted);
if (cryptStatus == kCCSuccess)
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSMutableData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
}
free(buffer); //free the buffer;
return nil; }
Result : t\PFLFC\^X\^C^\^^\^RWQV\^\ypt text with AES algorithm
Seem it alway wrong first 16bit block. Can u help me. what i'm wrong when encrypt ?
aesencryption.net performs AES in CBC mode. IV is a hardcoded ASCII string (16 chars). Message is UTF8-encoded then PKCS7-padded. AES key is the given password, UTF8-encoded then null-padded to choosen key size (or truncated if password is too long). And of course, the result is displayed as base64.
Getting the IV is as simple as encrypting a given block, decrypting it in ECB mode, and xoring that decrypted block with the original one...
Try to use AESCrypt-ObjC instead of Cryptlib Library.
Installation
Add this line to your class:
#import "AESCrypt.h"
Usage
NSString *message = #"top secret message";
NSString *password = #"p4ssw0rd";
Encrypting
NSString *encryptedData = [AESCrypt encrypt:message password:password];
Decrypting
NSString *message = [AESCrypt decrypt:encryptedData password:password];
Hope this will help.
Also, you can see this answer: https://stackoverflow.com/a/51767050/5167909
Your CCCryptorStatus parameters in osstatus :
should look like this
cryptStatus = CCCrypt( kCCDecrypt, kCCAlgorithmAES128,kCCOptionECBMode + kCCOptionPKCS7Padding, keyPtr, kCCKeySizeAES256 NULL,[self bytes], dataLength, buffer, bufferSize, &numBytesEncrypted );,
However you length of the key is less than 16 bytes. At least make sure that your secret key should be of 16 bytes.

AES ECB iOS Encrypt

I try to encrypt some string using AES algorithm with ECB option.
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionECBMode,
encryptionKey, kCCKeySizeAES128,
NULL /* initialization vector (optional) */,
[self bytes], dataLength, /* input */
buffer, bufferSize, /* output */
&numBytesEncrypted);
if (cryptStatus == kCCSuccess) {
return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
}
But func returns kCCAlignmentError (-4303)
Then I try to align data:
unsigned long diff = kCCKeySizeAES128 - (dataLength % kCCKeySizeAES128);
unsigned long newSize = 0;
if (diff > 0) {
newSize = dataLength + diff;
}
char dataPtr[newSize];
memcpy(dataPtr, [self bytes], [self length]);
for(int i = 0; i < diff; i++) {
dataPtr[i + dataLength] = 0x20;
}
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionECBMode,
encryptionKey, kCCKeySizeAES128,
NULL /* initialization vector (optional) */,
dataPtr, sizeof(dataPtr), /* input */
buffer, bufferSize, /* output */
&numBytesEncrypted);
input string
"test_string,test2"
result is
jxtFOhYpgBVieM90zx9oDanqBkcsVAvRRJsM4GL3cio=
On Android result is
jxtFOhYpgBVieM90zx9oDUfV7v43WFv7F5bzErfxrL8=
What did I wrong?
Simply AES is a block cypher which means it requires it's input data to be a multiple of the block size (16-bytes for AES). Your input data is 17 bytes thus th alignment error. (It is not talking about the alignment in memory).
The way to handle this is to specify PKCS#7 padding in the options:
kCCOptionPKCS7Padding | kCCOptionECBMode
The input data will be padded to a block multiple and on decryption the padding will be removed. To allow this on encryption it is necessary to increase the output buffer by one block size.
Consider not using [ECB mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_Codebook_.28ECB.29 (Scroll down to the Penguin), it is not secure.
If you are using mcrypt on Android: don't, it is abandonware and does not support standard padding, only null padding. Instead consider defuse or RNCryptor which is a full secure implementation is is available for iOS and Java.
If you do use mcrypt you will need to add your own PKCS#7 padding.
Here is example code:
+ (NSData *)doCipher:(NSData *)dataIn
key:(NSData *)symmetricKey
context:(CCOperation)encryptOrDecrypt // kCCEncrypt or kCCDecrypt
{
CCCryptorStatus ccStatus = kCCSuccess;
size_t cryptBytes = 0; // Number of bytes moved to buffer.
NSMutableData *dataOut = [NSMutableData dataWithLength:dataIn.length + kCCBlockSizeAES128];
ccStatus = CCCrypt( encryptOrDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding | kCCOptionECBMode,
symmetricKey.bytes,
kCCKeySizeAES128,
0,
dataIn.bytes, dataIn.length,
dataOut.mutableBytes, dataOut.length,
&cryptBytes);
if (ccStatus != kCCSuccess) {
NSLog(#"CCCrypt status: %d", ccStatus);
}
dataOut.length = cryptBytes;
return dataOut;
}
Example PHP PKCS#7 padding:
Add PKCS#7 padding
$padLength = $blockSize - (strlen($clearText) % $blockSize);
$clearText = $clearText . str_repeat(chr($padLength), $padLength);
Strip PKCS#7 padding
$padLength = ord($cryptText[strlen($cryptText)-1]);
$cryptText = substr($cryptText, 0, strlen($cryptText) - $padLength);
Although AES/ECB is not really recommended. This post explains why there's an alignment error and how to deal with it.
Alignment error almost means there is something wrong with the sizes.
Why is there an error?
A block cipher works on units of a fixed size (known as a block size), but messages come in a variety of lengths. So some modes (namely ECB and CBC) require that the final block be padded before encryption. (Source: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_Codebook_(ECB) )
Since AES ECB doesn't do the padding for you, you have to process the raw data before encrypting it.
Of course you can use kCCOptionPKCS7Padding when permitted, but since this thread is talking about ECB mode and alignment error, let's just focus on how to pad the data.
How many bytes should I pad?
The number of plain data bytes has to be an integral multiple of the current algorithm's block size.
That is, if your block size is kCCBlockSizeAES128 (16), you have to pad your data into "the nearest multiple of the block size" (16*n).
For example,
if your data is "abc" (3 bytes),
then you have to pad your data into 16 bytes;
if your data is "1234567890123456" (16 bytes),
then you have to pad it into 32 bytes.
0 ~ 15 bytes => pad to 16 bytes
16 ~ 31 bytes => pad to 32 bytes
32 ~ 47 bytes => pad to 48 bytes
... and so on
What is the value of the bytes to be padded?
If you have to pad 1 byte, then the 1 byte you pad would be '01';
If you have to pad 2 bytes, then the 2 bytes you pad would be '02';
...
If you have to pad 16 bytes, then the 16 bytes you pad would be '10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10'
Example of raw data and padded data before encryption
Example 1. (under block size 16)
Original data string: "abc" (3 bytes)
Data Bytes: "61 62 63"
Padded Data Bytes: "61 62 63 0d 0d 0d 0d 0d 0d 0d 0d 0d 0d 0d 0d 0d"
Explanation:
Data are 3 bytes, so the nearest multiple of the block size is 16 bytes.
So there are 13 bytes to be padded.
And 13 in hex would be 0xd, so '0d' is padded for 13 times.
Let's have another example of data that is exactly a multiple of block size.
Example 2. (under block size 16)
Original data string: "1234567890123456" (16 bytes)
Data Bytes: "31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36"
Padded Data Bytes: "31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10"
Explanation:
Data are 16 bytes, so the nearest multiple of the block size is 32 bytes.
So there are 16 bytes to be padded.
And 16 in hex would be 0x10, so '10' is padded for 16 times.
To pad the data in Objective-C
Here is an example of how to pad your data when using AES/ECB encryption mode:
+ (NSData *)addPaddingBeforeEncryptWithAESECB:(NSData *) data{
//Length has to be the nearest multiple of the block size
int cipherLen = (int)(data.length/kAlgorithmBlockSize + 1)*kAlgorithmBlockSize;
NSMutableData *newData = [NSMutableData dataWithLength:cipherLen];
newData = [data mutableCopy];
//How many bytes to be padded
int bytesToAddOn = kAlgorithmBlockSize - data.length%kAlgorithmBlockSize;
//Each byte in hex
char byteToAdd = bytesToAddOn & 0xff;
char *buffer = malloc(bytesToAddOn * sizeof byteToAdd);
memset (buffer, byteToAdd, sizeof (char) * bytesToAddOn);
[newData appendBytes:buffer length:bytesToAddOn];
return newData;
}
Full example of AES ECB encryption:
+ (NSData *)encryptDataWithAESECB:(NSData *)data
key:(NSData *) key
error:(NSError **)error {
size_t outLength;
int cipherLen = (int)(data.length/kAlgorithmBlockSize + 1)*kAlgorithmBlockSize;
NSMutableData *cipherData = [NSMutableData dataWithLength:cipherLen];
NSData *newData = [self addPaddingBeforeEncryptWithAESECB:data];
CCCryptorStatus result = CCCrypt(kCCEncrypt, // operation
kAlgorithm, // Algorithm
kCCOptionECBMode, // Mode
key.bytes, // key
key.length, // keylength
0,// iv
newData.bytes, // dataIn
newData.length, // dataInLength,
cipherData.mutableBytes, // dataOut
cipherData.length, // dataOutAvailable
&outLength); // dataOutMoved
if (result == kCCSuccess) {
cipherData.length = outLength;
}else {
if (error) {
*error = [NSError errorWithDomain:kRNCryptManagerErrorDomain code:result userInfo:nil];
}
return nil;
}
return cipherData;
}
What's next?
You have to strip off the extra bytes before AES ECB decryption.

H264 Video Streaming over RTMP on iOS

With a bit of digging, I have found a library that extracts NAL units from .mp4 file while it is being written. I'm attempting to packetize this information to flv over RTMP using libavformat and libavcodec. I setup a video stream using:
-(void)setupVideoStream {
int ret = 0;
videoCodec = avcodec_find_decoder(STREAM_VIDEO_CODEC);
if (videoCodec == nil) {
NSLog(#"Could not find encoder %i", STREAM_VIDEO_CODEC);
return;
}
videoStream = avformat_new_stream(oc, videoCodec);
videoCodecContext = videoStream->codec;
videoCodecContext->codec_type = AVMEDIA_TYPE_VIDEO;
videoCodecContext->codec_id = STREAM_VIDEO_CODEC;
videoCodecContext->pix_fmt = AV_PIX_FMT_YUV420P;
videoCodecContext->profile = FF_PROFILE_H264_BASELINE;
videoCodecContext->bit_rate = 512000;
videoCodecContext->bit_rate_tolerance = 0;
videoCodecContext->width = STREAM_WIDTH;
videoCodecContext->height = STREAM_HEIGHT;
videoCodecContext->time_base.den = STREAM_TIME_BASE;
videoCodecContext->time_base.num = 1;
videoCodecContext->gop_size = STREAM_GOP;
videoCodecContext->has_b_frames = 0;
videoCodecContext->ticks_per_frame = 2;
videoCodecContext->qcompress = 0.6;
videoCodecContext->qmax = 51;
videoCodecContext->qmin = 10;
videoCodecContext->max_qdiff = 4;
videoCodecContext->i_quant_factor = 0.71;
if (oc->oformat->flags & AVFMT_GLOBALHEADER)
videoCodecContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
videoCodecContext->extradata = avcCHeader;
videoCodecContext->extradata_size = avcCHeaderSize;
ret = avcodec_open2(videoStream->codec, videoCodec, NULL);
if (ret < 0)
NSLog(#"Could not open codec!");
}
Then I connect, and each time the library extracts a NALU, it returns an array holding one or two NALUs to my RTMPClient. The method that handles the actual streaming looks like this:
-(void)writeNALUToStream:(NSArray*)data time:(double)pts {
int ret = 0;
uint8_t *buffer = NULL;
int bufferSize = 0;
// Number of NALUs within the data array
int numNALUs = [data count];
// First NALU
NSData *fNALU = [data objectAtIndex:0];
int fLen = [fNALU length];
// If there is more than one NALU...
if (numNALUs > 1) {
// Second NALU
NSData *sNALU = [data objectAtIndex:1];
int sLen = [sNALU length];
// Allocate a buffer the size of first data and second data
buffer = av_malloc(fLen + sLen);
// Copy the first data bytes of fLen into the buffer
memcpy(buffer, [fNALU bytes], fLen);
// Copy the second data bytes of sLen into the buffer + fLen + 1
memcpy(buffer + fLen + 1, [sNALU bytes], sLen);
// Update the size of the buffer
bufferSize = fLen + sLen;
}else {
// Allocate a buffer the size of first data
buffer = av_malloc(fLen);
// Copy the first data bytes of fLen into the buffer
memcpy(buffer, [fNALU bytes], fLen);
// Update the size of the buffer
bufferSize = fLen;
}
// Initialize the packet
av_init_packet(&pkt);
//av_packet_from_data(&pkt, buffer, bufferSize);
// Set the packet data to the buffer
pkt.data = buffer;
pkt.size = bufferSize;
pkt.pts = pts;
// Stream index 0 is the video stream
pkt.stream_index = 0;
// Add a key frame flag every 15 frames
if ((processedFrames % 15) == 0)
pkt.flags |= AV_PKT_FLAG_KEY;
// Write the frame to the stream
ret = av_interleaved_write_frame(oc, &pkt);
if (ret < 0)
NSLog(#"Error writing frame %i to stream", processedFrames);
else {
// Update the number of frames successfully streamed
frameCount++;
// Update the number of bytes successfully sent
bytesSent += pkt.size;
}
// Update the number of frames processed
processedFrames++;
// Update the number of bytes processed
processedBytes += pkt.size;
free((uint8_t*)buffer);
// Free the packet
av_free_packet(&pkt);
}
After about 100 or so frames, I get an error:
malloc: *** error for object 0xe5bfa0: incorrect checksum for freed object - object was probably modified after being freed.
*** set a breakpoint in malloc_error_break to debug
Which I cannot seem to stop from happening. I've tried commenting out the av_free_packet() method and the free() along with trying to use av_packet_from_data() rather than initializing the packet and setting the data and size values.
My question is; how can I stop this error from happening, and according to wireshark, these are proper RTMP h264 packets, but they do not play anything more than a black screen. Is there some glaring error that I am overlooking?
It looks to me like you are overflowing your buffer and corrupting you stream here:
memcpy(buffer + fLen + 1, [sNALU bytes], sLen);
You are allocating fLen + sLen bytes then writing fLen + sLen + 1 bytes. Just get rid of the + 1.
Because your AVPacket is allocated on the stack av_free_packet() is not needed.
Finally, it is considered good practice to to allocate extra bytes for libav. av_malloc(size + FF_INPUT_BUFFER_PADDING_SIZE )

Decryption using CCCrypt returns kCCSuccess with bad buffer length

I have an encrypted data stream (AES 128, CBC, PKCS7) that I'm trying to decrypt as it arrives. Occasionally I'll get a packet of length 334, which I then try to decrypt. When I do this on an iPhone 5, it returns kCCBufferTooSmall (which is expected for non-mod 16 data). However, when I have the same thing on an iPhone 3GS it returns kCCSuccess and gives me a partially decrypted stream (the last ten bytes or so of the 333 it gives me are bogus - null terminators and random data).
Both devices are iOS 6.1.2. The app is built with base SDK set to latest SDK (6.1) with a deployment target of iOS 5.0.
I created the following test case which also exhibits this problem:
+ (void)decryptionTest {
NSData *data = [NSMutableData dataWithLength:334]; // 334 % 16 = 14
NSData *key = [NSMutableData dataWithLength:kCCKeySizeAES128];
NSData *iv = [NSMutableData dataWithLength:kCCBlockSizeAES128];
size_t outLength = 0;
NSMutableData *cipherData = [NSMutableData dataWithLength:data.length];
CCCryptorStatus result = CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
key.bytes,
key.length,
iv.bytes,
data.bytes,
data.length,
cipherData.mutableBytes,
cipherData.length,
&outLength);
NSLog(#"result = %d", result);
}
Why am I getting kCCSuccess when it should be failing due to not matching the block size?

Strange SecKeyEncrypt behaviour

I'm trying to implement RSA encryption with PKCS1 padding using SecKeyEncrypt function.
The code is following:
NSData *encryptText(NSString *text, SecKeyRef publicKey)
{
NSCParameterAssert(text.length > 0);
NSCParameterAssert(publicKey != NULL);
NSData *dataToEncrypt = [text dataUsingEncoding:NSUTF8StringEncoding];
const uint8_t *bytesToEncrypt = dataToEncrypt.bytes;
size_t cipherBufferSize = SecKeyGetBlockSize(publicKey);
NSCAssert(cipherBufferSize > 11, #"block size is too small: %zd", cipherBufferSize);
const size_t inputBlockSize = cipherBufferSize - 11; // since we'll use PKCS1 padding
uint8_t *cipherBuffer = (uint8_t *) malloc(sizeof(uint8_t) * cipherBufferSize);
NSMutableData *accumulator = [[NSMutableData alloc] init];
#try {
for (size_t block = 0; block * inputBlockSize < dataToEncrypt.length; block++) {
size_t blockOffset = block * inputBlockSize;
const uint8_t *chunkToEncrypt = (bytesToEncrypt + block * inputBlockSize);
const size_t remainingSize = dataToEncrypt.length - blockOffset;
const size_t subsize = remainingSize < inputBlockSize ? remainingSize : inputBlockSize;
size_t actualOutputSize = cipherBufferSize;
OSStatus status = SecKeyEncrypt(publicKey, kSecPaddingPKCS1, chunkToEncrypt, subsize, cipherBuffer, &actualOutputSize);
if (status != noErr) {
NSLog(#"Cannot encrypt data, last SecKeyEncrypt status: %ld", status);
return nil;
}
[accumulator appendBytes:cipherBuffer length:actualOutputSize];
}
return [accumulator copy];
}
#finally {
free(cipherBuffer);
}
}
It works perfectly on iOS 6, but fails on iOS 5, SecKeyEncrypt returns -50 (errSecParam). It would work on iOS 5 if I change 11 to 12 in inputBlockSize = cipherBufferSize - 11.
Apple doc says that input chunk length should be less or equal SecKeyGetBlockSize() - 11 if PKCS1 padding used. But on iOS 5 it definitely requires shorter input.
My key block size is 64, so input chunk max length is 53, according to docs. On iOS 5 only 52 or less would work.
What's wrong with this code? Or it's iOS 5 Security.framework bug?
UPD: problem reproduces only with 512-bit key. Tried with generated 1024-bit key, code works on iOS 5 with 11
Related Apple doc: http://developer.apple.com/library/ios/documentation/Security/Reference/certifkeytrustservices/Reference/reference.html#//apple_ref/c/func/SecKeyEncrypt

Resources