Read Me About CryptoCompatibility
CryptoCompatibility shows how to do common cryptographic operations using Apple APIs such that the results match other common cryptographic APIs, most notably OpenSSL.
CryptoCompatibility works on OS X 10.7 and later. For more information about compatibility, see the "Compatibility" section, below.
The sample contains the following items:
o Read Me About CryptoCompatibility.txt -- This file.
o CryptoCompatibility.xcodeproj -- An Xcode project for the sample.
o Operations -- Contains numerous NSOperation subclasses that implement various cryptographic operations. See "Supported Cryptographic Operations", below, for details.
o Tool -- Contains the source for the OS X test tool.
o UnitTest -- Unit tests for the above-mentioned operations.
o TestData -- Data to support the unit tests.
o build -- A pre-built binary.
Supported Cryptographic Operations
The sample demonstrates the following cryptographic operations:
o Base64 encode (QCCBase64Encode) and decode (QCCBase64Decode)
o MD5 digest (QCCMD5Digest)
o SHA1 digest (QCCSHA1Digest)
o HMAC-SHA1 message authentication code generation (QCCHMACSHA1Authentication)
o PBKDF2-SHA1 key derivation (QCCPBKDF2SHA1KeyDerivation)
o AES-128/192/256 encryption and decryption, both ECB and CBC mode, with padding (QCCAESPadCryptor) and without (QCCAESCryptor)
o AES-128/192/256 encryption and decryption, with support for large data sets, both ECB and CBC mode, with padding (QCCAESPadBigCryptor)
o RSA SHA1 sign (QCCRSASHA1Sign, QCCRSASHA1SignT) and verify (QCCRSASHA1Verify, QCCRSASHA1VerifyT)
o Low-level RSA encryption and decryption (QCCRSASmallCryptor, QCCRSASmallCryptorT), with no padding or PKCS#1 padding
In all cases the results match those same results generated by OpenSSL (as present on OS X 10.8.4), except for PBKDF2-SHA1 where the results were tested against PHP 5.5.0.a6.
Building and Testing the Sample
The sample was built using Xcode 4.6.3 on OS X 10.8.4. Within the project there are three targets:
o Tool -- This builds the OS X test tool.
o UnitTest -- This builds the OS X unit tests. You can run these unit tests by choosing Product > Test in one of two schemes:
- the "UnitTest" scheme itself
- the "Tool" scheme, but only after you've added these unit tests to the Test action of that scheme (choose Product > Scheme > Edit Scheme, select the Test action from the list on the left, select the Info tab, click the 'add' button under the "Tests" list, select "UnitTest" in the list, and click Add)
IMPORTANT: These unit tests expect to find a specific RSA key pair in your keychain. Before running them you should import those keys using the following commands:
$ security import TestData/public.pem -t pub -k ~/Library/Keychains/login.keychain
$ security import TestData/private.pem -t priv -k ~/Library/Keychains/login.keychain -A
These keys get the name "Imported Public Key" and "Imported Private Key" respectively. If your keychain already contains a keys with those names, you should use Keychain Access to rename the other keys before importing these keys.
Note: When running the unit tests you may see various "Assertion failure" messages in Xcode's console. These aren't a problem unless they cause the test to fail. See the "Other Notes" section (below) for more details on this.
o iOS UnitTest -- This builds the iOS unit test. To run this you must select its scheme, targeting an iOS simulator, and choose Product > Test.
Additionally, you can test the "CryptoCompatibility" command line tool against the OpenSSL command line tool (<x-man-page://1/openssl>) to verify that they both produce the same results. To do this:
1. change into the "CryptoCompatibility" directory
$ cd Downloads/CryptoCompatibility
2. if you haven't already imported the test RSA keys into your keychain, you should do so; see the instructions above
3. run the "ATestAgainstOpenSSL.py" script
IMPORTANT: This script uses the pre-built binary in the "build" directory. If you modify the tool and want to test your modifications, you must pass the path to your newly built tool as the first argument to the script.
Finally, the sample includes trivial Java and PHP programs that demonstrate how to use those environments to create results that are compatible with its operations. See the comments at the top of "UnitTest/ATestAgainstJava.java" and "UnitTest/ATestAgainstPHP.php" for more details.
The various cryptographic operations (the NSOperation subclasses in the "Operations" directory) are intended to be very modular. Each one is completely independent of all the other code in this sample. To use an operation's code in your project you can either:
o take the operation as a whole and add it to your project
o extract the code from the operation's -main method and integrate it into your code
Most of the sample's cryptographic operations work on both OS X and iOS. The only exception is the various RSA operations (QCCRSASHA1Sign, QCCRSASHA1SignT, QCCRSASHA1Verify, QCCRSASHA1VerifyT, QCCRSASmallCryptor, QCCRSASmallCryptorT), where each operation has two flavours:
o if the class name ends with "T", the operation uses the SecTransform API and is compatible with OS X
o otherwise the operation uses the SecKey API and is compatible with iOS
For example, QCCRSASHA1VerifyT is the OS X operation and QCCRSASHA1Verify is its iOS counterpart.
This is necessary because there is currently no single RSA API that works on both platforms.
To keep things simple the cryptographic operations were designed to work with relatively small amounts of data, such that the data easily fits in memory. It would be quite easy to enhance any given operation to support larger amounts of data (by reading a file, say) but I did not do this for everything because I wanted to keep the code as simple as possible.
To get an example of how much work it is to support large data sets, compare QCCAESPadCryptor (which is a one-shot, in-memory operation) and QCCAESPadBigCryptor (which is a chunk-wise operation that can work with an arbitrarily large data set)
The RSA encryption and decryption operations (QCCRSASmallCryptorT and QCCRSASmallCryptor) are restricted to /very/ small amounts of data. The comments at the top of each operation's header file explain why this is the case.
If you run the unit tests on OS X 10.7 or iOS 5, you'll find that the -testPBKDF2EmptySalt and -testPBKDF2Empty cases fail. This is because the implementation of CCKeyDerivationPBKDF on those systems does not work correctly with an empty salt. This shouldn't be a problem in practice because you should always use salt; not doing so significantly undermines the security of PBKDF2.
The -testRSASignError unit test fails on OS X 10.7.x because, on that platform, the underlying security transform does not error when you ask it to sign a block of data with a public key (a oversight that was corrected on 10.8). This shouldn't be a problem in practice because signing with a public key is a programming error.
If you look at the source code for the RSA operations unit test (RSAOperationsTestsT), you'll find that all the tests involving RSA without padding are disabled on OS X 10.7.x. This is because kSecPaddingNoneKey was not implemented prior to 10.8 <rdar://problem/9987765>. If you need RSA without padding on 10.7.x, you might be able to work around this by talking directly to CDSA, but I haven't tried that myself.
This limitation also means that the OpenSSL test script ("UnitTest/ATestAgainstOpenSSL.py") will fail if you run it on OS X 10.7.x because it tests the RSA-without-padding case. To get it working on 10.7.x, simply comment out the offending tests (anything where it invokes "openssl" with the "-raw" argument).
Due to Xcode limitations, the iOS unit tests can only be run on the simulator. This shouldn't be a problem because, for cryptographic work like this, the simulator is very accurate.
When running the unit tests you may see various "Assertion failure" messages in Xcode's console. These aren't a problem unless they cause the test to fail. These messages occur because the unit tests exercise common erroneous code paths. For example, this message:
8/15/13 4:14:33.358 PM otest *** Assertion failure in -[QCCBase64Encode initWithInputData:], /Users/test/Documents/CryptoCompatibility/Operations/QCCBase64Encode.m:64
is printed because the following NSParameterAssert (in -[QCCBase64Encode initWithInputData:]) fires:
- (id)initWithInputData:(NSData *)inputData
NSParameterAssert(inputData != nil);
and that fires because it's being specifically tested by the code in Base64OperationsTests:
STAssertThrows((void) [[QCCBase64Encode alloc] initWithInputData:nil], @"");
Credits and Version History
If you find any problems with this sample, please file a bug against it.
1.0 (Aug 2013) was the first shipping version.
Share and Enjoy
Apple Developer Technical Support
16 Aug 2013