私はこれまで2日間を費やし、自由に使えるすべてのソースを調べてきたので、これが最後の手段です。
公開鍵をiPhoneのキーチェーンに保存したX509証明書があります(現時点ではシミュレーターのみ)。ASP.NET側では、秘密鍵を使用して証明書ストアに証明書を取得しています。iPhoneで文字列を暗号化し、サーバーで復号化すると、CryptographicException
「不正なデータ」が表示されます。Array.Reverse
ロングショットのページで提案されたものを試しましたRSACryptoServiceProvider
が、役に立ちませんでした。
両側のbase-64ストリングを比較しましたが、それらは同じです。デコード後の生のバイト配列を比較しましたが、それらも同じです。サーバーで公開鍵を使用して暗号化すると、バイト配列はiPhoneのバージョンとは異なり、秘密鍵を使用して簡単に復号化できます。生のプレーンテキスト文字列は115文字なので、2048ビットキーの256バイトの制限内です。
iPhoneの暗号化方法は次のとおりです(CryptoExerciseサンプルアプリのwrapSymmetricKey
方法とほぼ同じです)。
+ (NSData *)encrypt:(NSString *)plainText usingKey:(SecKeyRef)key error:(NSError **)err
{
size_t cipherBufferSize = SecKeyGetBlockSize(key);
uint8_t *cipherBuffer = NULL;
cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
memset((void *)cipherBuffer, 0x0, cipherBufferSize);
NSData *plainTextBytes = [plainText dataUsingEncoding:NSUTF8StringEncoding];
OSStatus status = SecKeyEncrypt(key, kSecPaddingNone,
(const uint8_t *)[plainTextBytes bytes],
[plainTextBytes length], cipherBuffer,
&cipherBufferSize);
if (status == noErr)
{
NSData *encryptedBytes = [[[NSData alloc]
initWithBytes:(const void *)cipherBuffer
length:cipherBufferSize] autorelease];
if (cipherBuffer)
{
free(cipherBuffer);
}
NSLog(@"Encrypted text (%d bytes): %@",
[encryptedBytes length], [encryptedBytes description]);
return encryptedBytes;
}
else
{
*err = [NSError errorWithDomain:@"errorDomain" code:status userInfo:nil];
NSLog(@"encrypt:usingKey: Error: %d", status);
return nil;
}
}
そして、これがサーバー側のC#復号化方法です。
private string Decrypt(string cipherText)
{
if (clientCert == null)
{
// Get certificate
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach (var certificate in store.Certificates)
{
if (certificate.GetNameInfo(X509NameType.SimpleName, false) == CERT)
{
clientCert = certificate;
break;
}
}
}
using (var rsa = (RSACryptoServiceProvider)clientCert.PrivateKey)
{
try
{
var encryptedBytes = Convert.FromBase64String(cipherText);
var decryptedBytes = rsa.Decrypt(encryptedBytes, false);
var plaintext = Encoding.UTF8.GetString(decryptedBytes);
return plaintext;
}
catch (CryptographicException e)
{
throw(new ApplicationException("Unable to decrypt payload.", e));
}
}
}
私の疑いは、プラットフォーム間にいくつかのエンコーディングの問題があったことでした。1つはビッグエンディアンで、もう1つはリトルエンディアンであることは知っていますが、どちらがどちらであるか、またはどのように違いを克服するかについては十分にわかりません。Mac OS X、Windows、iPhoneはすべてリトルエンディアンなので、問題はありません。
新しい理論:OAEPパディングブール値をfalseに設定すると、デフォルトでPKCS#11.5パディングになります。、、、、およびの定義どうやら、PKCS#11.5SecKey
のみがあります。おそらく、MicrosoftのPKCS#1 1.5!= AppleのPKCS1であるため、パディングは暗号化のバイナリ出力に影響を与えています。に設定して使用してみましたが、それでも動作しませんでした。SecPadding
PKCS1
PKCS1MD2
PKCS1MD5
PKCS1SHA1
kSecPaddingPKCS1
fOAEP
false
kSecPaddingPKCS1
と同等です。理論の設計図に戻る…
その他の新たに試みられた理論:
- iPhoneの証明書(.cerファイル)は、サーバーのPKCS#12バンドル(.pfxファイル)とまったく同じではないため、機能しません。別の証明書ストアに.cerファイルをインストールし、サーバーで暗号化された文字列を正常にラウンドトリップしました。
- base-64への変換とサーバーへのPOSTの動作により、同じクラスのラウンドトリップには存在しない奇妙な結果が生じたため、最初にURLEncoding / Decodingを試し、次にiPhoneから生のバイナリを投稿し、それが等しいことを確認して、同じ不良データを取得しました;
- 元の文字列は125バイトだったので、UTF-8(ロングショット)で切り捨てられる可能性があると思ったので、44バイトの文字列に切り詰めましたが結果はありませんでした。
- System.Cryptographyライブラリを振り返って、適切なクラスを使用していることを確認し、 `RSAPKCS1KeyExchangeDeformatter`を発見し、新しい見込み客に大喜びし、まったく同じように動作したときに落胆しました。
成功!
iPhoneシミュレーターのキーチェーンに、いわば水が濁っていたいくつかのがらくたがあったことが判明しました。でキーチェーンDBを削除して~/Library/Application Support/iPhone Simulator/User/Library/Keychains/keychain-2-debug.db
再作成しましたが、正常に機能しました。よろしくお願いします。それは単純なものでしたが、自明ではなかったでしょう。(私が学んだ2つのこと:1)シミュレーターからアプリをアンインストールしても、キーチェーンエントリがクリアされないこと、および2)定期的に完全に新しく開始すること。)
注:キーチェーンファイルの一般的なパスは、iOSのバージョンによって異なります:〜/ Library / Application Support / iPhone Simulator / [version] /Library/Keychains/keychain-2-debug.db例:〜/ Library / Application Support / iPhone Simulator / 4.3 / Library / Keychains / keychain-2-debug.db