I've just been able to test Pike with the updated libnettle, and can confirm it seems fixed. Note that if that testcase included in the patch is the same as I posted, it's from Wycheproof, not from oss-fuzz.
Cheers, Josh
Hi,
I can work on those recommendations some time in the future.
Perhaps you could provide a better description about Nettle's origins?
Cheers, Josh
I don't see anything wrong with that. Note that a newer version of pike8.0 should be used (https://github.com/pikelang/Pike/tree/8.0) since various issues in the crypto code has been fixed recently.
Hi,
https://github.com/operasoftware/nettle-wycheproof-testsuite is used to test all test-cases from that json file. You can run all tests that I was able to make with in Pike by using pike main.pike D
(the 'D' flag produces verbose output).
You can test the ECDSA tests (which includes the file you linked) by running pike main.pike ECDSA D
.
I'm unable to compile Nettle right now, so owuld be best if you could this this?
Cheers, Josh
Hi,
While testing Crypto.RSA with Wycheproof, an invalid test popped up in relation to the decryption of cyphertext with null bytes prepended. It seems that Crypto.RSA()->decrypt() will decrypt a CT if nulls are prepended, but not appended (the latter being the expected behavior).
The following code is an example:
int main() {
string key = String.hex2string("308204bd020100300d06092a864886f70d0101010500048204a7308204a30201000282010100b3510a2bcd4ce644c5b594ae5059e12b2f054b658d5da5959a2fdf1871b808bc3df3e628d2792e51aad5c124b43bda453dca5cde4bcf28e7bd4effba0cb4b742bbb6d5a013cb63d1aa3a89e02627ef5398b52c0cfd97d208abeb8d7c9bce0bbeb019a86ddb589beb29a5b74bf861075c677c81d430f030c265247af9d3c9140ccb65309d07e0adc1efd15cf17e7b055d7da3868e4648cc3a180f0ee7f8e1e7b18098a3391b4ce7161e98d57af8a947e201a463e2d6bbca8059e5706e9dfed8f4856465ffa712ed1aa18e888d12dc6aa09ce95ecfca83cc5b0b15db09c8647f5d524c0f2e7620a3416b9623cadc0f097af573261c98c8400aa12af38e43cad84d0203010001028201001a502d0eea6c7b69e21d5839101f705456ed0ef852fb47fe21071f54c5f33c8ceb066c62d727e32d26c58137329f89d3195325b795264c195d85472f7507dbd0961d2951f935a26b34f0ac24d15490e1128a9b7138915bc7dbfa8fe396357131c543ae9c98507368d9ceb08c1c6198a3eda7aea185a0e976cd42c22d00f003d9f19d96ea4c9afcbfe1441ccc802cfb0689f59d804c6a4e4f404c15174745ed6cb8bc88ef0b33ba0d2a80e35e43bc90f350052e72016e75b00d357a381c9c0d467069ca660887c987766349fcc43460b4aa516bce079edd87ba164307b752c277ed9528ad3ba0bf1877349ed3b7966a6c240110409bf4d0fade0c68fdadd847fd02818100ec125cf37e310a2ff46263b9b2e0629d6390005ec88913d4fb71bd4dd856124498aaeba983d7ba2bd942e64d223feb7a23af4d605efeea6bd70d39afe99d35a3aa15e74a1768778093be0edd4a8d09b2def6dc9b67ff85764625c2e19236db4c401ce30a2572d3ecb4f969b7ad19c522c02d774465676e1a3776c54d6248348b02818100c2742abcd9897bd4b0b671f973fc82a8f84abf5705ff88dd41948623afe9dca60dc6543390767feaebeb539576ee8bfa61b5fcbca94a7cef75a09150c540fa9694dd8004ad23718c889049219369c99f4458d4afc148f6f07df87324a96d9cf7b385dd8622414a1832f9f29446f050c2d5a6407649dc41ab70e23b3dcc22c9870281810096a9798d250263400bb6277342881627e07cecdf91187b01b89ff47314188a7c20fb24800156d2c85d5666e8df6ceff9f9804ddfad80ff5767de56ecc029c72bf6c717df9f64daafc29acf9dc7908f9a0ad67e20e8949936ccba18d021a2c4febb04349a2b2047c4901385b6e5d0c691d118b33f81802b32ac272ef09e42fad50281800554f41b0b87f68a45722b3be0cf4ab1e165034c1a91002ab8f29e9ef9e2dab6fee7b2455bafb42037e9d2f7e533f348a147412fd72080be7c2633f5d802c91c39e6bcece3e675e59995033c55737020dad9e8b30d04b828adfb9304ad54a11a35a4f50709876ac5b118236ba76a4d7c9a291dd9607b169de1d182385691999f0281801c640189d9bfe8c623833210a76c420c6f44e5d760e259916cec2ae2b156456960fd95e2747660c389562250f055049cfab7e5c3039549384a7a2aaeb1c824d3af709482a8cf9b587022a00b1f0722db50f33cb26dc20dd2245d5265df61ee2983c938c2167dcee121fc4b4479c237e728cf633ab60a8c0ecd04fce7e3baa559");
string pre_ct = String.hex2string("00004501b4d669e01b9ef2dc800aa1b06d49196f5a09fe8fbcd037323c60eaf027bfb98432be4e4a26c567ffec718bcbea977dd26812fa071c33808b4d5ebb742d9879806094b6fbeea63d25ea3141733b60e31c6912106e1b758a7fe0014f075193faa8b4622bfd5d3013f0a32190a95de61a3604711bc62945f95a6522bd4dfed0a994ef185b28c281f7b5e4c8ed41176d12d9fc1b837e6a0111d0132d08a6d6f0580de0c9eed8ed105531799482d1e466c68c23b0c222af7fc12ac279bc4ff57e7b4586d209371b38c4c1035edd418dc5f960441cb21ea2bedbfea86de0d7861e81021b650a1de51002c315f1e7c12debe4dcebf790caaa54a2f26b149cf9e77d");
string app_ct = String.hex2string("4501b4d669e01b9ef2dc800aa1b06d49196f5a09fe8fbcd037323c60eaf027bfb98432be4e4a26c567ffec718bcbea977dd26812fa071c33808b4d5ebb742d9879806094b6fbeea63d25ea3141733b60e31c6912106e1b758a7fe0014f075193faa8b4622bfd5d3013f0a32190a95de61a3604711bc62945f95a6522bd4dfed0a994ef185b28c281f7b5e4c8ed41176d12d9fc1b837e6a0111d0132d08a6d6f0580de0c9eed8ed105531799482d1e466c68c23b0c222af7fc12ac279bc4ff57e7b4586d209371b38c4c1035edd418dc5f960441cb21ea2bedbfea86de0d7861e81021b650a1de51002c315f1e7c12debe4dcebf790caaa54a2f26b149cf9e77d0000");
string control_ct = String.hex2string("4501b4d669e01b9ef2dc800aa1b06d49196f5a09fe8fbcd037323c60eaf027bfb98432be4e4a26c567ffec718bcbea977dd26812fa071c33808b4d5ebb742d9879806094b6fbeea63d25ea3141733b60e31c6912106e1b758a7fe0014f075193faa8b4622bfd5d3013f0a32190a95de61a3604711bc62945f95a6522bd4dfed0a994ef185b28c281f7b5e4c8ed41176d12d9fc1b837e6a0111d0132d08a6d6f0580de0c9eed8ed105531799482d1e466c68c23b0c222af7fc12ac279bc4ff57e7b4586d209371b38c4c1035edd418dc5f960441cb21ea2bedbfea86de0d7861e81021b650a1de51002c315f1e7c12debe4dcebf790caaa54a2f26b149cf9e77d");
Crypto.RSA rsa = Standards.PKCS.parse_private_key(key);
write("%O\n", rsa->decrypt(pre_ct));
write("%O\n", rsa->decrypt(app_ct));
write("%O\n", rsa->decrypt(control_ct));
}
Given the pre_ct and control_ct are different, one would not expect per_ct to decrypt successfully.
Let me know if you need any more information!
While conducting some tests using Gmp.mpz()->probably_prime_p(), I've noticed that so-called 'negative primes' are handled inconsistently.
While testing using https://github.com/google/wycheproof/blob/master/testvectors/primality_test.json, some tests deal with so-called 'negative values of prime numbers' – "Some libraries accept the negative of a prime number as prime. For crypto libraries this just adds another potential pitfall".
Pike's probably_prime_p() seems to classify these as 'not a prime number'. However, there is a single test which probably_prime_p() classifies as a prime number: ff38e38e39.
The following code tests the testcases:
int main() {
array vals = ({
"feff",
"ff3b13b13b",
"ff38e38e39",
"ae4c415c9882b931",
"a6c0964fda6c0965",
"ff05050505050505050505050505050505",
"ff20ffbe0083fef8020ffbe0083fef8021",
"ff5075075075075075075075075075075075075075075075075075075075075075",
});
for(int i=0; i<sizeof(vals); i++) {
mixed p = Gmp.mpz(vals[i], 16)->probably_prime_p(10000);
if(p == 0) {
write("%s is not a prime.\n", vals[i]);
} else if (p == 2){
write("%s is a prime.\n", vals[i]);
} else {
write("%s is maybe a prime.\n", vals[i]);
}
}
return 0;
}
I would expect all of the tests to return the same result. However, the following is printed:
feff is not a prime.
ff3b13b13b is not a prime.
ff38e38e39 is a prime.
ae4c415c9882b931 is not a prime.
a6c0964fda6c0965 is not a prime.
ff05050505050505050505050505050505 is not a prime.
ff20ffbe0083fef8020ffbe0083fef8021 is not a prime.
ff5075075075075075075075075075075075075075075075075075075075075075 is not a prime.
It may not be a bug, but I thought I would report that the test produces inconsistent results; something I would not expect. Could the classification of ff38e38e39
being a prime be a bug?
If this is not unexpected, apologies for the false report, but best to report the unexpected result
Hi there,
While conducting some tests of Crypto.ECC.SECP_521R1->ECDSA(), I've come across a testcase which should be successfully verified, but it is not.
The code is as follows:
int main() {
string x = "00ee030cdb40abf70726866681f7b7fedc534190929c05a650bb928b894a5bbfe9577eea83c6331a796fa27ed9fac95d9ecacdfef6d61c925502b0afddc671463549";
string y = "0155606dd4cab19330c57c2ee740cd9c7c88bd88d95f840f315d525379dfeb7ea9bd3677b2185b92957f374317cc6124aacc8708075c4c05c95cbbc355bd692c3708";
string msg = "313233343030";
string sig = "30818702420090c8d0d718cb9d8d81094e6d068fb13c16b4df8c77bac676dddfe3e68855bed06b9ba8d0f8a80edce03a9fac7da561e24b1cd22d459239a146695a671f81f73aaf02413ee5a0a544b0842134629640adf5f0637087b04a442b1e6a22555dc1d8b93f8784f1ddd0cf90f75944cc2cd7ae373e5c2bac356a60ff9d08adfcdba3fa1b7a9d1d";
mixed state = Crypto.ECC.SECP_521R1->ECDSA();
state->set_public_key(Gmp.mpz(x, 16), Gmp.mpz(y, 16));
if(state->pkcs_verify(String.hex2string(msg), Crypto.SHA3_512, String.hex2string(sig)))
write("Success!\n");
return 0;
}
The test codes from https://github.com/google/wycheproof/blob/master/testvectors/ecdsa_secp521r1_sha512_test.json#L4279, and the explanation for the test is as follows: "Some implementations of ECDSA do not handle duplication and points at infinity correctly. This is a test vector that has been specially crafted to check for such an omission"
Please note: I have not tested this in Nettle itself, because I'm not 100% sure how to use the related functions in the C code.
Thank you.
Note this does not affect standard Nettle, and nettle's test-suite asserts when attempting to verify signature as expected.
Note: these issues also affect Crypto.ECC.ECDSA.
Hi,
While conducting some tests based on Wycheproof, Crypto.DSA fails one (out of ~70) test for "Modified r or s, e.g. by adding or subtracting the group order".
The following test should fail, but is verified as a legitimate signature:
int main() {
mapping(string:string) key = ([
"g" : "16a65c58204850704e7502a39757040d34da3a3478c154d4e4a5c02d242ee04f96e61e4bd0904abdac8f37eeb1e09f3182d23c9043cb642f88004160edf9ca09b32076a79c32a627f2473e91879ba2c4e744bd2081544cb55b802c368d1fa83ed489e94e0fa0688e32428a5c78c478c68d0527b71c9a3abb0b0be12c44689639e7d3ce74db101a65aa2b87f64c6826db3ec72f4b5599834bb4edb02f7c90e9a496d3a55d535bebfc45d4f619f63f3dedbb873925c2f224e07731296da887ec1e4748f87efb5fdeb75484316b2232dee553ddaf02112b0d1f02da30973224fe27aeda8b9d4b2922d9ba8be39ed9e103a63c52810bc688b7e2ed4316e1ef17dbde",
"p" : "008f7935d9b9aae9bfabed887acf4951b6f32ec59e3baf3718e8eac4961f3efd3606e74351a9c4183339b809e7c2ae1c539ba7475b85d011adb8b47987754984695cac0e8f14b3360828a22ffa27110a3d62a993453409a0fe696c4658f84bdd20819c3709a01057b195adcd00233dba5484b6291f9d648ef883448677979cec04b434a6ac2e75e9985de23db0292fc1118c9ffa9d8181e7338db792b730d7b9e349592f68099872153915ea3d6b8b4653c633458f803b32a4c2e0f27290256e4e3f8a3b0838a1c450e4e18c1a29a37ddf5ea143de4b66ff04903ed5cf1623e158d487c608e97f211cd81dca23cb6e380765f822e342be484c05763939601cd667",
"q" : "00baf696a68578f7dfdee7fa67c977c785ef32b233bae580c0bcd5695d",
"y" : "1e77f842b1ae0fcd9929d394161d41e14614ff7507a9a31f4a1f14d22e2a627a1f4e596624883f1a5b168e9425146f22d5f6ee28757414714bb994ba1129f015d6e04a717edf9b530a5d5cab94f14631e8b4cf79aeb358cc741845553841e8ac461630e804a62f43676ba6794af66899c377b869ea612a7b9fe6611aa96be52eb8b62c979117bbbcca8a7ec1e1ffab1c7dfcfc7048700d3ae3858136e897701d7c2921b5dfef1d1f897f50d96ca1b5c2edc58cada18919e35642f0807eebfa00c99a32f4d095c3188f78ed54711be0325c4b532aeccd6540a567c327225440ea15319bde06510479a1861799e25b57decc73c036d75a0702bd373ca231349931",
]);
string msg = String.hex2string("313233343030");
string sig = String.hex2string("303e021d00a545d62d6e336775fb6a9b8495721646a54bd8c6173fc0a2295a1b7b021d00c178f07615a75535ca0ee2274e824a59fef7f79ef575a73a1e040e05");
mixed state = Crypto.DSA.State();
state->set_public_key(Gmp.mpz(key["p"], 16), Gmp.mpz(key["q"], 16), Gmp.mpz(key["g"], 16), Gmp.mpz(key["y"], 16));
bool res = state->pkcs_verify(msg, Crypto.SHA224, sig);
if(res)
write("success!\n");
return 0;
}
Unfortunately I cannot offer more support on this, but if you have any questions, please let me know.
Cheers, Josh
Hi,
During some tests, I've noticed that Crypto.DSA.State()->pkcs_verify() verifies a PKCS signature even if the length of the ASN.1 signature contains both trailing, and appended, zeros. e.g. 0x00000123 is accepted, even though 0x0123 is the correct value: "This is a signature with correct values for (r, s) but using some alternative BER encoding instead of DER encoding. Implementations should not accept such signatures to limit signature malleability"
The following test should not succeed:
int main() {
mapping(string:string) key = ([
"g" : "16a65c58204850704e7502a39757040d34da3a3478c154d4e4a5c02d242ee04f96e61e4bd0904abdac8f37eeb1e09f3182d23c9043cb642f88004160edf9ca09b32076a79c32a627f2473e91879ba2c4e744bd2081544cb55b802c368d1fa83ed489e94e0fa0688e32428a5c78c478c68d0527b71c9a3abb0b0be12c44689639e7d3ce74db101a65aa2b87f64c6826db3ec72f4b5599834bb4edb02f7c90e9a496d3a55d535bebfc45d4f619f63f3dedbb873925c2f224e07731296da887ec1e4748f87efb5fdeb75484316b2232dee553ddaf02112b0d1f02da30973224fe27aeda8b9d4b2922d9ba8be39ed9e103a63c52810bc688b7e2ed4316e1ef17dbde",
"p" : "008f7935d9b9aae9bfabed887acf4951b6f32ec59e3baf3718e8eac4961f3efd3606e74351a9c4183339b809e7c2ae1c539ba7475b85d011adb8b47987754984695cac0e8f14b3360828a22ffa27110a3d62a993453409a0fe696c4658f84bdd20819c3709a01057b195adcd00233dba5484b6291f9d648ef883448677979cec04b434a6ac2e75e9985de23db0292fc1118c9ffa9d8181e7338db792b730d7b9e349592f68099872153915ea3d6b8b4653c633458f803b32a4c2e0f27290256e4e3f8a3b0838a1c450e4e18c1a29a37ddf5ea143de4b66ff04903ed5cf1623e158d487c608e97f211cd81dca23cb6e380765f822e342be484c05763939601cd667",
"q" : "00baf696a68578f7dfdee7fa67c977c785ef32b233bae580c0bcd5695d",
"y" : "1e77f842b1ae0fcd9929d394161d41e14614ff7507a9a31f4a1f14d22e2a627a1f4e596624883f1a5b168e9425146f22d5f6ee28757414714bb994ba1129f015d6e04a717edf9b530a5d5cab94f14631e8b4cf79aeb358cc741845553841e8ac461630e804a62f43676ba6794af66899c377b869ea612a7b9fe6611aa96be52eb8b62c979117bbbcca8a7ec1e1ffab1c7dfcfc7048700d3ae3858136e897701d7c2921b5dfef1d1f897f50d96ca1b5c2edc58cada18919e35642f0807eebfa00c99a32f4d095c3188f78ed54711be0325c4b532aeccd6540a567c327225440ea15319bde06510479a1861799e25b57decc73c036d75a0702bd373ca231349931",
]);
string msg = String.hex2string("313233343030");
string sig = String.hex2string("3082003d021d00a545d62d6e336775fb6a9b8495721646a54bd8c6173fc0a2295a1b7b021c068259cf902e5d55eb26e7bf850a82d40fc5456b3a902679612ea4a8");
mixed state = Crypto.DSA.State();
state->set_public_key(Gmp.mpz(key["p"], 16), Gmp.mpz(key["q"], 16), Gmp.mpz(key["g"], 16), Gmp.mpz(key["y"], 16));
bool res = state->pkcs_verify(msg, Crypto.SHA224, sig);
if(res)
write("success!\n");
return 0;
}
Some more information about this issue can be found here: https://github.com/kjur/jsrsasign/issues/437 (the issues "long form encoding of length of sequence", "length of sequence contains leading 0", and "prepending 0's to integer" all occur in Pike), and https://github.com/kjur/jsrsasign/security/advisories/GHSA-p8c3-7rj8-q963.
Please let me know if you need more information.
Cheers, Josh
Hi there,
While doing some tests of Crypto.DSA, I've come across two (likely related) cases of a call to Crypto.DSA.State()->pkcs_verify()
resulting in an infinite loop in Gmp.
I have two test-cases. One is where sign
is empty:
int main() {
mixed state1 = Crypto.DSA.State();
state1->set_public_key(Gmp.mpz("008f7935d9b9aae9bfabed887acf4951b6f32ec59e3baf3718e8eac4961f3efd3606e74351a9c4183339b809e7c2ae1c539ba7475b85d011adb8b47987754984695cac0e8f14b3360828a22ffa27110a3d62a993453409a0fe696c4658f84bdd20819c3709a01057b195adcd00233dba5484b6291f9d648ef883448677979cec04b434a6ac2e75e9985de23db0292fc1118c9ffa9d8181e7338db792b730d7b9e349592f68099872153915ea3d6b8b4653c633458f803b32a4c2e0f27290256e4e3f8a3b0838a1c450e4e18c1a29a37ddf5ea143de4b66ff04903ed5cf1623e158d487c608e97f211cd81dca23cb6e380765f822e342be484c05763939601cd667", 16), Gmp.mpz("00baf696a68578f7dfdee7fa67c977c785ef32b233bae580c0bcd5695d", 16), Gmp.mpz("16a65c58204850704e7502a39757040d34da3a3478c154d4e4a5c02d242ee04f96e61e4bd0904abdac8f37eeb1e09f3182d23c9043cb642f88004160edf9ca09b32076a79c32a627f2473e91879ba2c4e744bd2081544cb55b802c368d1fa83ed489e94e0fa0688e32428a5c78c478c68d0527b71c9a3abb0b0be12c44689639e7d3ce74db101a65aa2b87f64c6826db3ec72f4b5599834bb4edb02f7c90e9a496d3a55d535bebfc45d4f619f63f3dedbb873925c2f224e07731296da887ec1e4748f87efb5fdeb75484316b2232dee553ddaf02112b0d1f02da30973224fe27aeda8b9d4b2922d9ba8be39ed9e103a63c52810bc688b7e2ed4316e1ef17dbde", 16), Gmp.mpz("1e77f842b1ae0fcd9929d394161d41e14614ff7507a9a31f4a1f14d22e2a627a1f4e596624883f1a5b168e9425146f22d5f6ee28757414714bb994ba1129f015d6e04a717edf9b530a5d5cab94f14631e8b4cf79aeb358cc741845553841e8ac461630e804a62f43676ba6794af66899c377b869ea612a7b9fe6611aa96be52eb8b62c979117bbbcca8a7ec1e1ffab1c7dfcfc7048700d3ae3858136e897701d7c2921b5dfef1d1f897f50d96ca1b5c2edc58cada18919e35642f0807eebfa00c99a32f4d095c3188f78ed54711be0325c4b532aeccd6540a567c327225440ea15319bde06510479a1861799e25b57decc73c036d75a0702bd373ca231349931", 16));
state1->pkcs_verify(String.hex2string("313233343030"), Crypto.SHA224, "");
}
and the other is when it is non-empty:
int main() {
mixed state1 = Crypto.DSA.State();
state1->set_public_key(Gmp.mpz("008f7935d9b9aae9bfabed887acf4951b6f32ec59e3baf3718e8eac4961f3efd3606e74351a9c4183339b809e7c2ae1c539ba7475b85d011adb8b47987754984695cac0e8f14b3360828a22ffa27110a3d62a993453409a0fe696c4658f84bdd20819c3709a01057b195adcd00233dba5484b6291f9d648ef883448677979cec04b434a6ac2e75e9985de23db0292fc1118c9ffa9d8181e7338db792b730d7b9e349592f68099872153915ea3d6b8b4653c633458f803b32a4c2e0f27290256e4e3f8a3b0838a1c450e4e18c1a29a37ddf5ea143de4b66ff04903ed5cf1623e158d487c608e97f211cd81dca23cb6e380765f822e342be484c05763939601cd667", 16), Gmp.mpz("00baf696a68578f7dfdee7fa67c977c785ef32b233bae580c0bcd5695d", 16), Gmp.mpz("16a65c58204850704e7502a39757040d34da3a3478c154d4e4a5c02d242ee04f96e61e4bd0904abdac8f37eeb1e09f3182d23c9043cb642f88004160edf9ca09b32076a79c32a627f2473e91879ba2c4e744bd2081544cb55b802c368d1fa83ed489e94e0fa0688e32428a5c78c478c68d0527b71c9a3abb0b0be12c44689639e7d3ce74db101a65aa2b87f64c6826db3ec72f4b5599834bb4edb02f7c90e9a496d3a55d535bebfc45d4f619f63f3dedbb873925c2f224e07731296da887ec1e4748f87efb5fdeb75484316b2232dee553ddaf02112b0d1f02da30973224fe27aeda8b9d4b2922d9ba8be39ed9e103a63c52810bc688b7e2ed4316e1ef17dbde", 16), Gmp.mpz("1e77f842b1ae0fcd9929d394161d41e14614ff7507a9a31f4a1f14d22e2a627a1f4e596624883f1a5b168e9425146f22d5f6ee28757414714bb994ba1129f015d6e04a717edf9b530a5d5cab94f14631e8b4cf79aeb358cc741845553841e8ac461630e804a62f43676ba6794af66899c377b869ea612a7b9fe6611aa96be52eb8b62c979117bbbcca8a7ec1e1ffab1c7dfcfc7048700d3ae3858136e897701d7c2921b5dfef1d1f897f50d96ca1b5c2edc58cada18919e35642f0807eebfa00c99a32f4d095c3188f78ed54711be0325c4b532aeccd6540a567c327225440ea15319bde06510479a1861799e25b57decc73c036d75a0702bd373ca231349931", 16));
state1->pkcs_verify(String.hex2string("3130353336323835353638"), Crypto.SHA256, String.hex2string("a2184515521e4c5d26f05590543c696ca2bd04b7754a18107d7f62744fbcb3a52ee80de3dca53339c3f6b2196afe3c540adfeb92686029f2"));
}
Unfortunately, I'm not able to offer any suggestions as to why this happens, but please let me know if you have any questions.
Cheers, Josh
AFAICT, this issue only affects Pike. Using the Nettle library in C results in the proper digest. This can be done by editing nettle's testsuite/test-ccm.c and adding
test_cipher_ccm(&nettle_aes128,
SHEX("1a44f3550688fddbc1e5041dc98952c0"),
SHEX("5d2904298f668ba95eaa1797"),
SHEX("d55908958b70abee81054cdf3d3df5"), 1,
SHEX(""),
SHEX("5c71b4f069cfa13b7634db4b13e7be7d"));
make ccm-test ; ./ccm-test
succeeds.
Hi there,
While performing some unit tests with Pike's Crypto.AES.CCM, I've run into an issue of the digest function producing "incorrect" results.
During my tests, I'm using two unit tests. The first test is:
key: 1a44f3550688fddbc1e5041dc98952c0
iv: 5d2904298f668ba95eaa1797
aad: d55908958b70abee81054cdf3d3df5
msg:
expected digest: 5c71b4f069cfa13b7634db4b13e7be7d
And the second test is:
key: 439fd5c3b76587d5a601ba6ef8fad214
iv: ed1d316d0834d174c1b5b438
aad: eae252f42d2c71
msg:
expected digest: e8530426cbabf63633ff373159247e38
The second test results in the the digest value of "e8530426cbabf63633ff373159247e38". This is the same value as seen in openssl using the following C source code (gcc test.c -lcrypto
):
#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
void str2hex(char *, char*, int);
void printBytes(unsigned char *, size_t );
int main() {
unsigned char *aad, *pt, *key, *nonce;
int Klen, Alen, Nlen, Plen, Tlen, Clen;
int outl = 0;
key = "439fd5c3b76587d5a601ba6ef8fad214";
aad = "eae252f42d2c71";
nonce = "ed1d316d0834d174c1b5b438";
pt = "";
Klen = strlen(key) / 2;
Alen = strlen(aad) / 2;
Nlen = strlen(nonce) / 2;
Plen = strlen(pt) / 2;
Tlen = 16;
Clen = Plen + Tlen;
unsigned char keyy[Klen], aadd[Alen], noncee[Nlen], ptt[Plen];
unsigned char ct[Clen], dt[Plen];
str2hex(key, keyy, Klen);
str2hex(pt, ptt, Plen);
str2hex(aad, aadd, Alen);
str2hex(nonce, noncee, Nlen);
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX_init(ctx);
EVP_EncryptInit(ctx, EVP_aes_128_ccm(), 0, 0);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, Nlen, 0);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, Tlen, 0);
EVP_EncryptInit(ctx, 0, keyy, noncee);
EVP_EncryptUpdate(ctx, 0, &outl, 0, Plen);
EVP_EncryptUpdate(ctx, 0, &outl, aadd, Alen);
EVP_EncryptUpdate(ctx, ct, &outl, ptt, Plen);
EVP_EncryptFinal(ctx, &ct[outl], &outl);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_GET_TAG, Tlen, ct + Plen);
printf("plaintext' = %s", pt);
printf("\n");
printf("\nciphertext : ");
printBytes(ct, Clen);
EVP_DecryptInit(ctx, EVP_aes_128_ccm(), 0, 0);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, Nlen, 0);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, Tlen, ct + Plen);
EVP_DecryptInit(ctx, 0, keyy, noncee);
EVP_DecryptUpdate(ctx, 0, &outl, 0, Plen);
EVP_DecryptUpdate(ctx, 0, &outl, aadd, Alen);
EVP_DecryptUpdate(ctx, dt, &outl, ct, Plen);
EVP_DecryptFinal(ctx, &dt[outl], &outl);
printf("plaintext : ");
printBytes(dt, Plen);
return 0;
}
void str2hex(char *str, char *hex, int len) {
int tt, ss;
unsigned char temp[4];
for (tt = 0, ss = 0; tt < len, ss < 2 * len; tt++, ss += 2) {
temp[0] = '0';
temp[1] = 'x';
temp[2] = str[ss];
temp[3] = str[ss + 1];
hex[tt] = (int) strtol(temp, NULL, 0);
}
}
void printBytes(unsigned char *buf, size_t len) {
int i;
for (i = 0; i < len; i++) {
printf("%02x", buf[i]);
}
printf("\n");
}
However, the first test does not succeed. Pike produces the digest "7a627cad3a11cb4192566a040d801fa8", while the C code (edited accordingly) produces the expected result, "5c71b4f069cfa13b7634db4b13e7be7d".
The following Pike code annotates my concerns:
int main() {
mixed state1 = Crypto.AES.CCM.State();
state1->set_encrypt_key(String.hex2string("1a44f3550688fddbc1e5041dc98952c0"));
state1->set_iv(String.hex2string("5d2904298f668ba95eaa1797"));
state1->update(String.hex2string("d55908958b70abee81054cdf3d3df5"));
string ct1 = state1->crypt(String.hex2string(""));
string dig1 = state1->digest();
if(String.string2hex(dig1) != "5c71b4f069cfa13b7634db4b13e7be7d")
write("First one did not match. Got %s, expected %s.\n", String.string2hex(dig1), "5c71b4f069cfa13b7634db4b13e7be7d");
mixed state2 = Crypto.AES.CCM.State();
state2->set_encrypt_key(String.hex2string("439fd5c3b76587d5a601ba6ef8fad214"));
state2->set_iv(String.hex2string("ed1d316d0834d174c1b5b438"));
state2->update(String.hex2string("eae252f42d2c71"));
string ct2 = state2->crypt(String.hex2string(""));
string dig2 = state2->digest();
if(String.string2hex(dig2) != "e8530426cbabf63633ff373159247e38")
write("Second one did not match. Got %s, expected %s.\n", String.string2hex(dig2), "e8530426cbabf63633ff373159247e38");
}
I have no explanation for the incorrect results, unfortunately.
Any support is welcome.
Thank you.
Hi,
Crypto.AES.CCM currently raises an exception if a too-short (less than 7-octets) IV is set using state->set_iv().
According to https://pike.lysator.liu.se/generated/manual/modref/ex/predef_3A_3A/Nettle/BlockCipher/CTR/State/set_iv.html, "iv must have the length reported by iv_size().".
However, a too-long IV set using set_iv() does not raise an exception, and is instead simply truncated to 13-octets:
if (iv_len < 7) {
Pike_error("Too short nonce for CCM. Must be at least 7 bytes.\n");
}
if (THIS->nonce) {
free_string(THIS->nonce);
THIS->nonce = NULL;
}
if (iv_len > 13) {
THIS->nonce = string_slice(iv, 0, 12);
iv_len = 13;
} else {
add_ref(THIS->nonce = iv);
}
As far as I can tell, this truncation is undocumented, and personally, I see no reason for it to be truncated over an exception being raised.
Can the documentation be changed and/or an exception be raised instead?
Thank you.