Backend iOS iap receipt validation without /verifyReceipt request - ios

I want to validate iOS in-app purchase receipts in my backend code.
Apple's design decision to do this using an external /verifyReceipt request is plain down stupid: it incurs latency and adds complexity of network error handling. Even more so, the data in the receipt looks like it can be public key verified.
After a bit of analysis on the signature field of a receipt, it seems to contain a PK-verified SHA1 hash:
<?php
$sig="ApxQMks+KAE0riYtKjNNwhNeuGQ6R98X223zCh60s9m8wloydP3sCceQdzrCwd/3N1L+dlefT7ZJUiquCEsDAo+Rh54eSovcKEk+2RZyoP/zRQHgTF81kYBIbkFCADhj6kzJVr1rYsRXKpOJk6qWMYPz+a90XJfGtnIDuHlRb4V5AAADVzCCA1MwggI7oAMCAQICCGUUkU3ZWAS1MA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEzMDEGA1UEAwwqQXBwbGUgaVR1bmVzIFN0b3JlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA5MDYxNTIyMDU1NloXDTE0MDYxNDIyMDU1NlowZDEjMCEGA1UEAwwaUHVyY2hhc2VSZWNlaXB0Q2VydGlmaWNhdGUxGzAZBgNVBAsMEkFwcGxlIGlUdW5lcyBTdG9yZTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMrRjF2ct4IrSdiTChaI0g8pwv/cmHs8p/RwV/rt/91XKVhNl4XIBimKjQQNfgHsDs6yju++DrKJE7uKsphMddKYfFE5rGXsAdBEjBwRIxexTevx3HLEFGAt1moKx509dhxtiIdDgJv2YaVs49B0uJvNdy6SMqNNLHsDLzDS9oZHAgMBAAGjcjBwMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUNh3o4p2C0gEYtTJrDtdDC5FYQzowDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBSpg4PyGUjFPhJXCBTMzaN+mV8k9TAQBgoqhkiG92NkBgUBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAEaSbPjtmN4C/IB3QEpK32RxacCDXdVXAeVReS5FaZxc+t88pQP93BiAxvdW/3eTSMGY5FbeAYL3etqP5gm8wrFojX0ikyVRStQ+/AQ0KEjtqB07kLs9QUe8czR8UGfdM1EumV/UgvDd4NwNYxLQMg4WTQfgkQQVy8GXZwVHgbE/UC6Y7053pGXBk51NPM3woxhd3gSRLvXj+loHsStcTEqe9pBDpmG5+sk4tw+GK3GMeEN5/+e1QT9np/Kl1nj+aBw7C0xsy0bFnaAd1cSS6xdory/CUvM6gtKsmnOOdqTesbp0bs8sn6Wqs0C9dgcxRHuOMZ2tm8npLUm7argOSzQ==";
file_put_contents('sig', substr(base64_decode($sig),1,128));
file_put_contents('cert.der', substr(base64_decode($sig),133));
# show certificate
echo `openssl x509 -in cert.der -inform der -noout -text` . "\n\n";
# convert to pem
`openssl x509 -in cert.der -inform der -out cert.pem`;
echo "signature:\n";
echo `openssl rsautl -in sig -verify -asn1parse -inkey cert.pem -certin`;
echo "\n\n";
Output:
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
65:14:91:4d:d9:58:04:b5
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, O=Apple Inc., OU=Apple Certification Authority, CN=Apple iTunes Store Certification Authority
Validity
Not Before: Jun 15 22:05:56 2009 GMT
Not After : Jun 14 22:05:56 2014 GMT
Subject: CN=PurchaseReceiptCertificate, OU=Apple iTunes Store, O=Apple Inc., C=US
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:ca:d1:8c:5d:9c:b7:82:2b:49:d8:93:0a:16:88:
d2:0f:29:c2:ff:dc:98:7b:3c:a7:f4:70:57:fa:ed:
ff:dd:57:29:58:4d:97:85:c8:06:29:8a:8d:04:0d:
7e:01:ec:0e:ce:b2:8e:ef:be:0e:b2:89:13:bb:8a:
b2:98:4c:75:d2:98:7c:51:39:ac:65:ec:01:d0:44:
8c:1c:11:23:17:b1:4d:eb:f1:dc:72:c4:14:60:2d:
d6:6a:0a:c7:9d:3d:76:1c:6d:88:87:43:80:9b:f6:
61:a5:6c:e3:d0:74:b8:9b:cd:77:2e:92:32:a3:4d:
2c:7b:03:2f:30:d2:f6:86:47
Exponent: 65537 (0x10001)
--- cut ---
signature:
0:d=0 hl=2 l= 33 cons: SEQUENCE
2:d=1 hl=2 l= 9 cons: SEQUENCE
4:d=2 hl=2 l= 5 prim: OBJECT :sha1
11:d=2 hl=2 l= 0 prim: NULL
13:d=1 hl=2 l= 20 prim: OCTET STRING
0000 - b7 ef f1 9e 01 2a dd 26-09 38 cd ce 63 5b b1 32 .....*.&.8..c[.2
0010 - 88 51 17 0a .Q..
The question now remains of which data this is a hash. The above contains an actual signature of this receipt:
{
"signature" = "ApxQMks+KAE0riYtKjNNwhNeuGQ6R98X223zCh60s9m8wloydP3sCceQdzrCwd/3N1L+dlefT7ZJUiquCEsDAo+Rh54eSovcKEk+2RZyoP/zRQHgTF81kYBIbkFCADhj6kzJVr1rYsRXKpOJk6qWMYPz+a90XJfGtnIDuHlRb4V5AAADVzCCA1MwggI7oAMCAQICCGUUkU3ZWAS1MA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEzMDEGA1UEAwwqQXBwbGUgaVR1bmVzIFN0b3JlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA5MDYxNTIyMDU1NloXDTE0MDYxNDIyMDU1NlowZDEjMCEGA1UEAwwaUHVyY2hhc2VSZWNlaXB0Q2VydGlmaWNhdGUxGzAZBgNVBAsMEkFwcGxlIGlUdW5lcyBTdG9yZTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMrRjF2ct4IrSdiTChaI0g8pwv/cmHs8p/RwV/rt/91XKVhNl4XIBimKjQQNfgHsDs6yju++DrKJE7uKsphMddKYfFE5rGXsAdBEjBwRIxexTevx3HLEFGAt1moKx509dhxtiIdDgJv2YaVs49B0uJvNdy6SMqNNLHsDLzDS9oZHAgMBAAGjcjBwMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUNh3o4p2C0gEYtTJrDtdDC5FYQzowDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBSpg4PyGUjFPhJXCBTMzaN+mV8k9TAQBgoqhkiG92NkBgUBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAEaSbPjtmN4C/IB3QEpK32RxacCDXdVXAeVReS5FaZxc+t88pQP93BiAxvdW/3eTSMGY5FbeAYL3etqP5gm8wrFojX0ikyVRStQ+/AQ0KEjtqB07kLs9QUe8czR8UGfdM1EumV/UgvDd4NwNYxLQMg4WTQfgkQQVy8GXZwVHgbE/UC6Y7053pGXBk51NPM3woxhd3gSRLvXj+loHsStcTEqe9pBDpmG5+sk4tw+GK3GMeEN5/+e1QT9np/Kl1nj+aBw7C0xsy0bFnaAd1cSS6xdory/CUvM6gtKsmnOOdqTesbp0bs8sn6Wqs0C9dgcxRHuOMZ2tm8npLUm7argOSzQ==";
"purchase-info" = "ewoJIm9yaWdpbmFsLXB1cmNoYXNlLWRhdGUtcHN0IiA9ICIyMDE0LTAxLTI3IDA0OjUwOjU2IEFtZXJpY2EvTG9zX0FuZ2VsZXMiOwoJInB1cmNoYXNlLWRhdGUtbXMiID0gIjEzOTA4MjcwNTYxNzciOwoJInVuaXF1ZS1pZGVudGlmaWVyIiA9ICJiMTUxOTczZThjYzY3ZGRlMzkwNzhiZDAwMGU4N2U3MjNiYjE0M2U1IjsKCSJvcmlnaW5hbC10cmFuc2FjdGlvbi1pZCIgPSAiMTAwMDAwMDA5OTcwODE1MCI7CgkiYnZycyIgPSAiMS4wLjciOwoJImFwcC1pdGVtLWlkIiA9ICI3MTE0MTEzMTciOwoJInRyYW5zYWN0aW9uLWlkIiA9ICIxMDAwMDAwMDk5NzA4MTUwIjsKCSJxdWFudGl0eSIgPSAiMSI7Cgkib3JpZ2luYWwtcHVyY2hhc2UtZGF0ZS1tcyIgPSAiMTM5MDgyNzA1NjE3NyI7CgkidW5pcXVlLXZlbmRvci1pZGVudGlmaWVyIiA9ICJCNjNBRTVFQy02MjBCLTQxMkEtQjE5NC03NUI3MDU3Mjk4M0MiOwoJIml0ZW0taWQiID0gIjc3NTg5MTg5MiI7CgkidmVyc2lvbi1leHRlcm5hbC1pZGVudGlmaWVyIiA9ICIxNzU5NTMxMzgiOwoJInByb2R1Y3QtaWQiID0gImNvbS5pbnRvbXlsaWZlLmNyZWRpdHMyNSI7CgkicHVyY2hhc2UtZGF0ZSIgPSAiMjAxNC0wMS0yNyAxMjo1MDo1NiBFdGMvR01UIjsKCSJvcmlnaW5hbC1wdXJjaGFzZS1kYXRlIiA9ICIyMDE0LTAxLTI3IDEyOjUwOjU2IEV0Yy9HTVQiOwoJImJpZCIgPSAiY29tLmludG9teWxpZmUuaW9zIjsKCSJwdXJjaGFzZS1kYXRlLXBzdCIgPSAiMjAxNC0wMS0yNyAwNDo1MDo1NiBBbWVyaWNhL0xvc19BbmdlbGVzIjsKfQ==";
"environment" = "Sandbox";
"pod" = "100";
"signing-status" = "0";
}
Or, in base64:
$receipt="ewoJInNpZ25hdHVyZSIgPSAiQXB4UU1rcytLQUUwcmlZdEtqTk53aE5ldUdRNlI5OFgyMjN6Q2g2MHM5bTh3bG95ZFAzc0NjZVFkenJDd2QvM04xTCtkbGVmVDdaSlVpcXVDRXNEQW8rUmg1NGVTb3ZjS0VrKzJSWnlvUC96UlFIZ1RGODFrWUJJYmtGQ0FEaGo2a3pKVnIxcllzUlhLcE9KazZxV01ZUHorYTkwWEpmR3RuSUR1SGxSYjRWNUFBQURWekNDQTFNd2dnSTdvQU1DQVFJQ0NHVVVrVTNaV0FTMU1BMEdDU3FHU0liM0RRRUJCUVVBTUg4eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURXpNREVHQTFVRUF3d3FRWEJ3YkdVZ2FWUjFibVZ6SUZOMGIzSmxJRU5sY25ScFptbGpZWFJwYjI0Z1FYVjBhRzl5YVhSNU1CNFhEVEE1TURZeE5USXlNRFUxTmxvWERURTBNRFl4TkRJeU1EVTFObG93WkRFak1DRUdBMVVFQXd3YVVIVnlZMmhoYzJWU1pXTmxhWEIwUTJWeWRHbG1hV05oZEdVeEd6QVpCZ05WQkFzTUVrRndjR3hsSUdsVWRXNWxjeUJUZEc5eVpURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd2daOHdEUVlKS29aSWh2Y05BUUVCQlFBRGdZMEFNSUdKQW9HQkFNclJqRjJjdDRJclNkaVRDaGFJMGc4cHd2L2NtSHM4cC9Sd1YvcnQvOTFYS1ZoTmw0WElCaW1LalFRTmZnSHNEczZ5anUrK0RyS0pFN3VLc3BoTWRkS1lmRkU1ckdYc0FkQkVqQndSSXhleFRldngzSExFRkdBdDFtb0t4NTA5ZGh4dGlJZERnSnYyWWFWczQ5QjB1SnZOZHk2U01xTk5MSHNETHpEUzlvWkhBZ01CQUFHamNqQndNQXdHQTFVZEV3RUIvd1FDTUFBd0h3WURWUjBqQkJnd0ZvQVVOaDNvNHAyQzBnRVl0VEpyRHRkREM1RllRem93RGdZRFZSMFBBUUgvQkFRREFnZUFNQjBHQTFVZERnUVdCQlNwZzRQeUdVakZQaEpYQ0JUTXphTittVjhrOVRBUUJnb3Foa2lHOTJOa0JnVUJCQUlGQURBTkJna3Foa2lHOXcwQkFRVUZBQU9DQVFFQUVhU2JQanRtTjRDL0lCM1FFcEszMlJ4YWNDRFhkVlhBZVZSZVM1RmFaeGMrdDg4cFFQOTNCaUF4dmRXLzNlVFNNR1k1RmJlQVlMM2V0cVA1Z204d3JGb2pYMGlreVZSU3RRKy9BUTBLRWp0cUIwN2tMczlRVWU4Y3pSOFVHZmRNMUV1bVYvVWd2RGQ0TndOWXhMUU1nNFdUUWZna1FRVnk4R1had1ZIZ2JFL1VDNlk3MDUzcEdYQms1MU5QTTN3b3hoZDNnU1JMdlhqK2xvSHNTdGNURXFlOXBCRHBtRzUrc2s0dHcrR0szR01lRU41LytlMVFUOW5wL0tsMW5qK2FCdzdDMHhzeTBiRm5hQWQxY1NTNnhkb3J5L0NVdk02Z3RLc21uT09kcVRlc2JwMGJzOHNuNldxczBDOWRnY3hSSHVPTVoydG04bnBMVW03YXJnT1N6UT09IjsKCSJwdXJjaGFzZS1pbmZvIiA9ICJld29KSW05eWFXZHBibUZzTFhCMWNtTm9ZWE5sTFdSaGRHVXRjSE4wSWlBOUlDSXlNREUwTFRBeExUSTNJREEwT2pVd09qVTJJRUZ0WlhKcFkyRXZURzl6WDBGdVoyVnNaWE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0YlhNaUlEMGdJakV6T1RBNE1qY3dOVFl4TnpjaU93b0pJblZ1YVhGMVpTMXBaR1Z1ZEdsbWFXVnlJaUE5SUNKaU1UVXhPVGN6WlRoall6WTNaR1JsTXprd056aGlaREF3TUdVNE4yVTNNak5pWWpFME0yVTFJanNLQ1NKdmNtbG5hVzVoYkMxMGNtRnVjMkZqZEdsdmJpMXBaQ0lnUFNBaU1UQXdNREF3TURBNU9UY3dPREUxTUNJN0Nna2lZblp5Y3lJZ1BTQWlNUzR3TGpjaU93b0pJbUZ3Y0MxcGRHVnRMV2xrSWlBOUlDSTNNVEUwTVRFek1UY2lPd29KSW5SeVlXNXpZV04wYVc5dUxXbGtJaUE5SUNJeE1EQXdNREF3TURrNU56QTRNVFV3SWpzS0NTSnhkV0Z1ZEdsMGVTSWdQU0FpTVNJN0Nna2liM0pwWjJsdVlXd3RjSFZ5WTJoaGMyVXRaR0YwWlMxdGN5SWdQU0FpTVRNNU1EZ3lOekExTmpFM055STdDZ2tpZFc1cGNYVmxMWFpsYm1SdmNpMXBaR1Z1ZEdsbWFXVnlJaUE5SUNKQ05qTkJSVFZGUXkwMk1qQkNMVFF4TWtFdFFqRTVOQzAzTlVJM01EVTNNams0TTBNaU93b0pJbWwwWlcwdGFXUWlJRDBnSWpjM05UZzVNVGc1TWlJN0Nna2lkbVZ5YzJsdmJpMWxlSFJsY201aGJDMXBaR1Z1ZEdsbWFXVnlJaUE5SUNJeE56VTVOVE14TXpnaU93b0pJbkJ5YjJSMVkzUXRhV1FpSUQwZ0ltTnZiUzVwYm5SdmJYbHNhV1psTG1OeVpXUnBkSE15TlNJN0Nna2ljSFZ5WTJoaGMyVXRaR0YwWlNJZ1BTQWlNakF4TkMwd01TMHlOeUF4TWpvMU1EbzFOaUJGZEdNdlIwMVVJanNLQ1NKdmNtbG5hVzVoYkMxd2RYSmphR0Z6WlMxa1lYUmxJaUE5SUNJeU1ERTBMVEF4TFRJM0lERXlPalV3T2pVMklFVjBZeTlIVFZRaU93b0pJbUpwWkNJZ1BTQWlZMjl0TG1sdWRHOXRlV3hwWm1VdWFXOXpJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxMWEJ6ZENJZ1BTQWlNakF4TkMwd01TMHlOeUF3TkRvMU1EbzFOaUJCYldWeWFXTmhMMHh2YzE5QmJtZGxiR1Z6SWpzS2ZRPT0iOwoJImVudmlyb25tZW50IiA9ICJTYW5kYm94IjsKCSJwb2QiID0gIjEwMCI7Cgkic2lnbmluZy1zdGF0dXMiID0gIjAiOwp9";
The most simple data to verify would be the purchase-info field. Unfortunately, the sha1 sum of this (either base64-encoded or opaque) data does not match the one in the signature.
Having seen a fair share of Apple file formats, my guess would be it could be a combination in a form like "{$purchaseData}\x00\x00{$environment}\x00\x00{$pod}". With a bit of bad luck, though, they added a secret string that would make the entire exercise quite futile (but I fail to see as to why they would...)
Any insight?
update
Some more experimentation with sending different receipts to the /verifyReceipt endpoint suggests the pod/environment fields do not matter. Even more so, the order of fields within the receipt structure is of no consequence. Changing a single byte in the purchase-info data, however, directly yields an invalid receipt. This all would strengthen the hypothesis that only the purchase-info value is part of the hash -- but it's probably prefixed/suffixed with a secret. Could anyone verify (pun intended) this?

Please look at the code in https://developer.apple.com/library/ios/releasenotes/StoreKit/IAP_ReceiptValidation/index.html
The signature structure is
#pragma pack(push, 1)
struct signature_blob {
uint8_t version;
uint8_t signature[128];
uint32_t cert_len;
uint8_t certificate[];
} *signature_blob_ptr = (struct signature_blob *)signature_bytes;
#pragma pack(pop)
to_be_hashed= signature_blob_ptr->version . base64_decode(purchase_info)
code here
<?php
$plist = file_get_contents($argv[1]);
if(preg_match('/"signature" = "([^"]+)"/',$plist,$s) && preg_match('/"purchase-info" = "([^"]+)"/',$plist,$p)){
$sig= base64_decode($s[1]);
$purchase=base64_decode($p[1]);
$version=substr($sig,0,1);
$cert = substr($sig,133);
$sig = substr($sig,1,128);
$to_be_sign=$version.$purchase;
//get PEM encode
$cert="-----BEGIN CERTIFICATE-----\n".wordwrap(base64_encode($cert),64,"\n",true)."\n-----END CERTIFICATE-----";
$pubkey = openssl_get_publickey($cert);
if(openssl_verify($to_be_sign,$sig,$pubkey) ==1 )
echo "Success\n";
else
echo "Failed\n";
openssl_free_key($pubkey);
}

Related

SSL handshake failure (40) between nginx and iOS 11 only

I have an nginx 1.10.3 server running NextCloud and access it from various clients. The certificate is provided by Lets Encrypt and uses a 2048-bit RSA key.
All clients work fine, including web browsers, except those running on iOS 11 on an iPad or iPhone. The working browsers are Firefox 56 on MacOS and Linux, and Safari 11 on MacOS Sierra. The NextCloud client on Linux also works fine. On iOS, GoodReader has no problem accessing NextCloud as a webdav client. But Safari will not access it, claiming it could not access a secure connection to the server. The iOS NextCloud client returns an SSL error when it tries to connect (I presume it uses the same library as Safari to connect).
The error in the nginx log for when iOS (Safari or the NextCloud app) tries and fails to connect is:
SSL_do_handshake() failed (SSL: error:1417A0C1:SSL routines:tls_post_process_client_hello:no shared cipher) while SSL handshaking
I looked at the traffic on the web server and this is the Client Hello for Firefox:
Secure Sockets Layer
SSL Record Layer: Handshake Protocol: Client Hello
Content Type: Handshake (22)
Version: TLS 1.0 (0x0301)
Length: 512
Handshake Protocol: Client Hello
Handshake Type: Client Hello (1)
Length: 508
Version: TLS 1.2 (0x0303)
Random
GMT Unix Time: Aug 8, 2013 06:38:14.000000000 JST
Random Bytes: eece37d08b453cedc932958165d0b6c530b31a321554c874...
Session ID Length: 32
Session ID: c7...
Cipher Suites Length: 30
Cipher Suites (15 suites)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca9)
Cipher Suite: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca8)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA (0x0033)
Cipher Suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA (0x0039)
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035)
Cipher Suite: TLS_RSA_WITH_3DES_EDE_CBC_SHA (0x000a)
Compression Methods Length: 1
Compression Methods (1 method)
Compression Method: null (0)
Extensions Length: 405
Extension: server_name
Type: server_name (0x0000)
Length: 30
Server Name Indication extension
Server Name list length: 28
Server Name Type: host_name (0)
Server Name length: 25
Server Name: mydomain.com
Extension: Extended Master Secret
Type: Extended Master Secret (0x0017)
Length: 0
Extension: renegotiation_info
Type: renegotiation_info (0xff01)
Length: 1
Renegotiation Info extension
Renegotiation info extension length: 0
Extension: elliptic_curves
Type: elliptic_curves (0x000a)
Length: 10
Elliptic Curves Length: 8
Elliptic curves (4 curves)
Elliptic curve: ecdh_x25519 (0x001d)
Elliptic curve: secp256r1 (0x0017)
Elliptic curve: secp384r1 (0x0018)
Elliptic curve: secp521r1 (0x0019)
Extension: ec_point_formats
Type: ec_point_formats (0x000b)
Length: 2
EC point formats Length: 1
Elliptic curves point formats (1)
EC point format: uncompressed (0)
Extension: SessionTicket TLS
Type: SessionTicket TLS (0x0023)
Length: 208
Data (208 bytes)
Extension: Application Layer Protocol Negotiation
Type: Application Layer Protocol Negotiation (0x0010)
Length: 14
ALPN Extension Length: 12
ALPN Protocol
ALPN string length: 2
ALPN Next Protocol: h2
ALPN string length: 8
ALPN Next Protocol: http/1.1
Extension: status_request
Type: status_request (0x0005)
Length: 5
Certificate Status Type: OCSP (1)
Responder ID list Length: 0
Request Extensions Length: 0
Extension: signature_algorithms
Type: signature_algorithms (0x000d)
Length: 24
Signature Hash Algorithms Length: 22
Signature Hash Algorithms (11 algorithms)
Signature Hash Algorithm: 0x0403
Signature Hash Algorithm Hash: SHA256 (4)
Signature Hash Algorithm Signature: ECDSA (3)
Signature Hash Algorithm: 0x0503
Signature Hash Algorithm Hash: SHA384 (5)
Signature Hash Algorithm Signature: ECDSA (3)
Signature Hash Algorithm: 0x0603
Signature Hash Algorithm Hash: SHA512 (6)
Signature Hash Algorithm Signature: ECDSA (3)
Signature Hash Algorithm: 0x0804
Signature Hash Algorithm Hash: Unknown (8)
Signature Hash Algorithm Signature: Unknown (4)
Signature Hash Algorithm: 0x0805
Signature Hash Algorithm Hash: Unknown (8)
Signature Hash Algorithm Signature: Unknown (5)
Signature Hash Algorithm: 0x0806
Signature Hash Algorithm Hash: Unknown (8)
Signature Hash Algorithm Signature: Unknown (6)
Signature Hash Algorithm: 0x0401
Signature Hash Algorithm Hash: SHA256 (4)
Signature Hash Algorithm Signature: RSA (1)
Signature Hash Algorithm: 0x0501
Signature Hash Algorithm Hash: SHA384 (5)
Signature Hash Algorithm Signature: RSA (1)
Signature Hash Algorithm: 0x0601
Signature Hash Algorithm Hash: SHA512 (6)
Signature Hash Algorithm Signature: RSA (1)
Signature Hash Algorithm: 0x0203
Signature Hash Algorithm Hash: SHA1 (2)
Signature Hash Algorithm Signature: ECDSA (3)
Signature Hash Algorithm: 0x0201
Signature Hash Algorithm Hash: SHA1 (2)
Signature Hash Algorithm Signature: RSA (1)
Extension: Padding
Type: Padding (0x0015)
Length: 71
Padding Data: 000000000000000000000000000000000000000000000000...
Ultimately, the TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030) cipher is chosen for Firefox.
Here is the Client Hello for the iPad via Safari:
Secure Sockets Layer
SSL Record Layer: Handshake Protocol: Client Hello
Content Type: Handshake (22)
Version: TLS 1.0 (0x0301)
Length: 239
Handshake Protocol: Client Hello
Handshake Type: Client Hello (1)
Length: 235
Version: TLS 1.2 (0x0303)
Random
GMT Unix Time: Jul 20, 2002 17:04:33.000000000 JST
Random Bytes: 8f8602de9622cf56d70fa8d863a3c8d7154eb23ce19b625b...
Session ID Length: 0
Cipher Suites Length: 40
Cipher Suites (20 suites)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 (0xc024)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 (0xc023)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca9)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (0xc028)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (0xc027)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
Cipher Suite: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca8)
Cipher Suite: TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d)
Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c)
Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA256 (0x003d)
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA256 (0x003c)
Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035)
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
Compression Methods Length: 1
Compression Methods (1 method)
Compression Method: null (0)
Extensions Length: 154
Extension: renegotiation_info
Type: renegotiation_info (0xff01)
Length: 1
Renegotiation Info extension
Renegotiation info extension length: 0
Extension: server_name
Type: server_name (0x0000)
Length: 30
Server Name Indication extension
Server Name list length: 28
Server Name Type: host_name (0)
Server Name length: 25
Server Name: mydomain.com
Extension: Extended Master Secret
Type: Extended Master Secret (0x0017)
Length: 0
Extension: signature_algorithms
Type: signature_algorithms (0x000d)
Length: 20
Signature Hash Algorithms Length: 18
Signature Hash Algorithms (9 algorithms)
Signature Hash Algorithm: 0x0403
Signature Hash Algorithm Hash: SHA256 (4)
Signature Hash Algorithm Signature: ECDSA (3)
Signature Hash Algorithm: 0x0804
Signature Hash Algorithm Hash: Unknown (8)
Signature Hash Algorithm Signature: Unknown (4)
Signature Hash Algorithm: 0x0401
Signature Hash Algorithm Hash: SHA256 (4)
Signature Hash Algorithm Signature: RSA (1)
Signature Hash Algorithm: 0x0503
Signature Hash Algorithm Hash: SHA384 (5)
Signature Hash Algorithm Signature: ECDSA (3)
Signature Hash Algorithm: 0x0805
Signature Hash Algorithm Hash: Unknown (8)
Signature Hash Algorithm Signature: Unknown (5)
Signature Hash Algorithm: 0x0501
Signature Hash Algorithm Hash: SHA384 (5)
Signature Hash Algorithm Signature: RSA (1)
Signature Hash Algorithm: 0x0806
Signature Hash Algorithm Hash: Unknown (8)
Signature Hash Algorithm Signature: Unknown (6)
Signature Hash Algorithm: 0x0601
Signature Hash Algorithm Hash: SHA512 (6)
Signature Hash Algorithm Signature: RSA (1)
Signature Hash Algorithm: 0x0201
Signature Hash Algorithm Hash: SHA1 (2)
Signature Hash Algorithm Signature: RSA (1)
Extension: status_request
Type: status_request (0x0005)
Length: 5
Certificate Status Type: OCSP (1)
Responder ID list Length: 0
Request Extensions Length: 0
Extension: next_protocol_negotiation
Type: next_protocol_negotiation (0x3374)
Length: 0
Extension: signed_certificate_timestamp
Type: signed_certificate_timestamp (0x0012)
Length: 0
Data (0 bytes)
Extension: Application Layer Protocol Negotiation
Type: Application Layer Protocol Negotiation (0x0010)
Length: 48
ALPN Extension Length: 46
ALPN Protocol
ALPN string length: 2
ALPN Next Protocol: h2
ALPN string length: 5
ALPN Next Protocol: h2-16
ALPN string length: 5
ALPN Next Protocol: h2-15
ALPN string length: 5
ALPN Next Protocol: h2-14
ALPN string length: 8
ALPN Next Protocol: spdy/3.1
ALPN string length: 6
ALPN Next Protocol: spdy/3
ALPN string length: 8
ALPN Next Protocol: http/1.1
Extension: ec_point_formats
Type: ec_point_formats (0x000b)
Length: 2
EC point formats Length: 1
Elliptic curves point formats (1)
EC point format: uncompressed (0)
Extension: elliptic_curves
Type: elliptic_curves (0x000a)
Length: 8
Elliptic Curves Length: 6
Elliptic curves (3 curves)
Elliptic curve: ecdh_x25519 (0x001d)
Elliptic curve: secp256r1 (0x0017)
Elliptic curve: secp384r1 (0x0018)
The response given to iOS in the network traffic is:
Secure Sockets Layer
TLSv1.2 Record Layer: Alert (Level: Fatal, Description: Handshake Failure)
Content Type: Alert (21)
Version: TLS 1.2 (0x0303)
Length: 2
Alert Message
Level: Fatal (2)
Description: Handshake Failure (40)
Unfortunately what specifically failed in the handshake is not clear to me because I haven't been able to find any more detailed information. The exact cipher chosen by the server for Firefox is listed as supported by iOS, but the SSL handshake not only doesn't choose it but fails to choose any option.
Adding further weirdness, a run of the site through SSL Labs gives, alongside an A+ rating, the following results for the handshake test for Safari:
Safari 6/iOS 6.0.1 RSA 2048 (SHA256) TLS 1.2 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA ECDH secp521r1 FS
Safari 7/iOS 7.1 RSA 2048 (SHA256) TLS 1.2 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA ECDH secp521r1 FS
Safari 7/OS X 10.9 RSA 2048 (SHA256) TLS 1.2 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA ECDH secp521r1 FS
Safari 8/iOS 8.4 RSA 2048 (SHA256) TLS 1.2 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA ECDH secp521r1 FS
Safari 8/OS X 10.10 RSA 2048 (SHA256) TLS 1.2 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA ECDH secp521r1 FS
Safari 9/iOS 9 RSA 2048 (SHA256) TLS 1.2 > h2 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ECDH secp521r1 FS
Safari 9/OS X 10.11 RSA 2048 (SHA256) TLS 1.2 > h2 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ECDH secp521r1 FS
Safari 10/iOS 10 RSA 2048 (SHA256) TLS 1.2 > h2 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ECDH secp521r1 FS
Safari 10/OS X 10.12 RSA 2048 (SHA256) TLS 1.2 > h2 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ECDH secp521r1 FS
So apparently SSL Lab's model of Safari is happy with my server, but the real Safari on iOS is not.
This is the SSL cipher configuration for the nginx server:
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:
ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:
ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:
ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:
ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:
DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:
DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:
DHE-RSA-AES256-SHA256";
It was taken from here. I have also tried a configuration from the Mozilla SSL configuration generator. The "modern" profile was this:
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:
ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:
ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:
ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:
ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
This gave the same result of a handshake failure.
Letting nginx use the default for ssl_ciphers results in iOS connecting to the server. But, when I checked the TCP dump, I found that it had chosen the cipher TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d), which does not provide forward secrecy Naturally, SSL Labs also gives the site an A- rating when the server is using the default cipher selections, due to some browsers choosing ciphers without forward secrecy. But even in this situation, SSL Labs' Safari 10/iOS 10 handshake test gives RSA 2048 (SHA256) TLS 1.2 > h2 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ECDH secp521r1 FS for its result, so I'm stumped as to why iOS 11 is behaving so differently.
Any help in resolving this bizarre situation is greatly appreciated.
I have found the source of the problem. The server was constrained to only accept secp521r1 for the elliptical curve (the ssl_ecdh_curve setting). I don't remember why it was set like that; some guide in the past told me to do so and I blindly complied.
Adding an additional curve of lower strength, secp384r1, allows iOS to make a successful handshake and communicate using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384. ssl_ecdh_curve is now set to secp521r1:secp384r1.
I'm not sure what changed between iOS 10 and 11 to cause this. My best guess, from digging through the OpenSSL code and some further research, is that iOS 11 is trying to comply with Suite B. Suite B restricts the curves to P-256 and P-384. But that's just an amateur's guess.

iOS compression_encode_buffer doesn't include zlib header?

I'm using compression_encode_buffer with COMPRESSION_ZLIB to zlib-compress data in an iOS app, but the result is missing the zlib header.
For example:
size = compression_encode_buffer(
destinationBuffer, destinationBufferSize,
sourceBuffer, sourceBufferSize, nil,
COMPRESSION_ZLIB
)
Will return the bytes:
AD 53 C1 8E 9B 30 …
While using, for example, Python's data.encode("zlib") on the same data will return:
78 9C AD 53 C1 8E 9B 30 …
^^ ^^ correct zlib header
What's up with that? Why isn't the header being included? And is there a "correct" way to add it?
The two bytes are a zlib header, not a magic number. Most likely you are missing the final 4 bytes of the stream (the ADLER32 checksum) as well, and only have the "deflate" datastream that you expected to be wrapped in a zlib datatstream. The iOS documentation says as much:
ZLIB
The encoded format is the raw DEFLATE format as described
in IETF RFC 1951 Using the ZLIB library
They should have called the compression method "DEFLATE" not "ZLIB".
See this related question about dealing with ZLIB vs DEFLATE data.

NDEF format for WiFI Pairing Data

I have derived the WiFI Authentication and Encryption data format by writing it and then reading it from an application running on microcontroller.
Can some one please tell me the macros or values for the Authentication type (WPA-Personel,Shared,WPA2-Enterprise,WpA2-Enterprise,WPA2-Personel)fields and Encryption type fields (None,WEP,TKIP,ES,AES/TKIP).
I have figured out for WPA/WPA2-Personel authentication type and AES encryption type so far as below .
52 0x10 0x03 2 Attribute ID: Authentication Type
54 0x00 0x02 2 Attribute Length: 2 octets
56 0x00 0x20 2 Authentication Type: WPA2-Personal
58 0x10 0x0F 2 Attribute ID: Encryption Type
60 0x00 0x02 2 Attribute Length: 2 octets
62 0x00 0x08 2 Encryption Type: AES
Any official documentation will be highly appreciated.

Convert Lockbox2 cipher text to Lockbox3 cipher text

Is there a way I can Convert my Lockbox 2 Cipher text to LockBox 3 Cipher text.
We are migrating our application built on Delphi 2007 to Delphi xe2, we used the Lockbox 2 RSA Encryption algorithm in Delphi 2007 and we intend to use lockbox 3 in Delphi xe2 to support Unicode data. since the cipher text generated by both of them differs as Xe2 supports Unicode data we face a problem . So we would like to convert the cipher text that is generated by Lockbox 2 to LockBox 3 somehow.
Since your cipher text by definition is unrecognizable, there is no easy way to tell if the underlying plaintext data was Ansi or Unicode....so you likely need to manage a new associated property.
It obviously depends on the layout of your application and where this data is stored and how the clients are going to be upgraded, but there could be a new version flag of some sort associated with the stored ciphertext. If it's in a local table say, add a new column for PlainTextVersion and set the version to some value to flag that the ciphertext was saved from Unicode plaintext. When reading the ciphertext and this new field doesn't match the Unicdoe flag, you could upgrade the ciphertext by decrypting, and encrypting using Unicode plaintext, and then re-save the ciphertext and set the new flag (or simply put-off the ciphertext version upgrade until the plaintext has changed and needs to be updated.)
Or, better yet, auto-upgrade all current ciphertext at one time if feasible.
To convert, it would be easiest to use Lockbox 2 to decrypt your cypher text and use Lockbox 3 to reencrypt it.
The reason is that from what I can tell, Lockbox 2 stuffed up the implementation of the RSA block type 2 padding which means that Lockbox 2's RSA encryption is not compatible with anybody else's RSA decryption.
Lockbox 2's RSA encryption pads out the message incorrectly as follows (found by placing a breakpoint and inspecting the memory at biBlock.Fi.IntBuf.pBuf):
message-bytes 0x00 random-padding-bytes 0x02 0x00
e.g. 'test' was padded to:
$01C883AC 74 65 73 74 00 D4 50 50 test..PP
$01C883B4 A7 BO E5 51 7A 4C C2 BC ...QzL..
$01C883BC 8C B8 69 8A 97 DF AA 1D ..I.....
$01C883C4 78 67 1E OE 8B AB 02 00 xg......
But it should be padded out to (e.g. look at this worked example):
0x00 0x02 random-padding-bytes 0x00 message-bytes
Lockbox 2 isn't just storing the bytes in reverse (otherwise the message "test" would also be reversed) or reversed 32 bit little endian (otherwise the 02 00 would be swapped too). Everything works so long as you use Lockbox 2 for both encryption and decryption.
Also I noticed another bug where Lockbox 2 calls e.RandomSimplePrime() to generate the public exponent e but it generates an even number i.e. a fairly noteworthy bug in RandomSimplePrime() eh? I only looked at Lockbox 2.07. Lockbox 3 was a complete rewrite, so it won't have these bugs.

AES decryption in iOS: PKCS5 padding and CBC

I am implementing for iOS some decryption code for a message originating on a server over which I have no control. A previous implementation on another platform documents the decryption requirements AES256, specifies the key and the initialization vector, and also says:
* Cipher Mode: CBC
* Padding: PKCS5Padding
The options for creation of a CCCryptor object include only kCCOptionPKCS7Padding and kCCOptionECBMode, noting that CBC is the default. From what what I understand about padding for encryption, I don't understand how one might use both; I thought they were mutually exclusive. In creating a CCCryptor for the decryption, I have tried using both a 0 for options and kCCOptionPKCS7Padding, but both give me gibberish after decryption.
I have compared the dump of this decryption with a dump of the decoded byte buffer on the other platform and confirmed that they really are different. So there is something that I am doing different in this implementation that is significantly different, I just don't know what... And don't have a clue as to how to get a handle on it. The platforms are different enough that it is difficult to infer much from the previous implementation because it is based on a very different platform. And of course, the author of the previous implementation has since departed.
Any guesses what else could be incompatible or how to troubleshoot this thing?
PKCS#5 padding and PKCS#7 padding are practically the same (adding bytes 01, or 0202, or 0303 etc up to the length of the block size of the algorithm, 16 bytes in this case). Officially PKCS#5 padding should only be used for 8 byte blocks, but in many runtimes the two can be interchanged without issue. Padding always occurs at the end of the ciphertext, so if you get just gibberish it's not the padding. ECB is a block mode of operation (that should not be used to encrypt data that can be distinguished from random numbers) : it would require padding, so the two are not mutually exclusive.
Finally, if you just perform decryption (not MAC'ing or other forms of integrity control), and you return the result of the unpadding to the server (decryption failed), your plain text data is not safe because of padding oracle attacks.
First, you can worry about the padding later. Providing 0 like you have done means AES CBC with no padding, and with that configuration you should see your message just fine. Albiet potentially with some padding bytes on the end. So that leaves:
You're not loading the key correctly.
You're not loading the IV correctly.
You're not loading the data correctly.
The server is doing something you don't expect.
To debug this, you need to isolate your system. You can do this by implementing a loopback test where you both encrypt and then decrypt the data to make sure you're loading everything correctly. But that can be misleading. Even if you do something wrong (e.g., loading the key backwards), you could still be able to decrypt what you've encrypted because you're doing it exactly the same wrong way on both sides.
So you need to test against Known Answer Tests (KATs). You can look up the official KATs on the AES wikipedia entry. But it just so happens that I have posted another answer here on SO that we can use.
Given this input:
KEY: 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
IV: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
PLAIN TEXT: encrypt me
CIPHER TEXT: 338d2a9e28208cad84c457eb9bd91c81
Verify with a third-party program that you can decrypt the cipher text and get the plain text.
$ echo -n "encrypt me" > to_encrypt
$ openssl enc -in to_encrypt -out encrypted -e -aes-256-cbc \
> -K 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f \
> -iv 0000000000000000
$ hexdump -C encrypted
00000000 33 8d 2a 9e 28 20 8c ad 84 c4 57 eb 9b d9 1c 81 |3.*.( ....W.....|
00000010
$ openssl enc -in encrypted -out plain_text -d -aes-256-cbc \
> -K 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f \
> -iv 0000000000000000
$ hexdump -C plain_text
00000000 65 6e 63 72 79 70 74 20 6d 65 |encrypt me|
0000000a
So now try to decrypt this known-answer test in your program. Be sure to enable PKCS7 padding, because that's what I used in this example. As an exercise, decrypt it with no padding and see that the result is the same, except you have padding bytes after the "encrypt me" text.
Implementing the KAT is a big step. It says that your implementation correct, but your assumptions on the server's behavior is wrong. And then it's time to start questioning those assumptions...
(And P.S., those options you mentioned are not mutually exclusive. ECB means no IV, and CBC means you have an IV. No relation to padding.)
OK, I know I said it's an exercise, but I want to prove that even if you encrypt with padding
and decrypt without padding, you do not get garbage. So given the KAT that used PKCS7 padding, we decrypt it with the no padding option and get a readable message followed by 06 used as a padding byte.
$ openssl enc -in encrypted -out plain_text -d -aes-256-cbc \
-K 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f \
-iv 0000000000000000 -nopad
$ hexdump -C plain_text
00000000 65 6e 63 72 79 70 74 20 6d 65 06 06 06 06 06 06 |encrypt me......|
00000010
$
Paul,
The PKCS#5 padding is needed to identify padding in the decrypted data. For CBC, the input buffer must be a multiple of the cipher block size (16 for AES). For that reason, the buffer to be encrypted is extended with additional bytes. Note that after encryption, the original size of the data is lost. PKCS#5 padding allows to retrieve that size. This is done by filling the extended data buffer with repeated bytes, with value equal to the padding size. e.g if your cleartext buffer was 12 bytes, to make it multiple of 16, you will need to add 4 bytes more. (If the data was 16, you will add 16 more to make it 32). Then you fill those 4 bytes with '0x4' to conform with PKCS#5 padding. When you decrypt, simply look for the last byte in the decrypted data and subtract that number from the length of the decrypted buffer.
What you are doing is padding with '0's. Although you seem to be happy to see the results, you will get a surprise when your original data ends in one of more '0's.
It turns out that the explanation for the what I was experiencing was embarrassingly simple: I misinterpreted something I read in the previous implementation to imply that it was using a 256-bit key, but in fact it was using a 128-bit key. Make that change and all of the sudden what was obscure becomes cleartext. :-)
0 for the options argument, to invoke CBC, was in fact correct. What the reference to PKCS5 padding in the previous implementation is still mysterious, but that doesn't matter because because what I've got now works.
Thanks for the shot, indiv.

Resources