Hi all,
I am trying to perform a Diffie Hellman key exchange with a device using the new BLE APIs in the 3.1 beta 3 release of the SDK. However, the KeyPair generation and createPublicKey method in the Cryptography module is not working as expected. Disclaimer though: I am only mildly familiar with cryptography, so this might be an issue as to which algorithm is being used rather than an issue with my implementation.
The device supports the "FIPS P-256 Elliptic Curve" algorithm (quoted because that came directly from the datasheet). Connect IQ supports the "256-bit secp256r1 Elliptic Curve" algorithm (this came directly from the API docs). My understanding from this this page from IBM is that those are different names for the same curve (please correct me if I am wrong on this). The problem occurs when I send and subsequently receive the public keys to and from the device. After receiving the key, the device sends an error message and immediately disconnects. Additionally, the key that I receive (before it disconnects) is handled by createPublicKey(bytes), which throws the following exception:
Exception: The public key provided is not valid for the selected algorithm
So I started doing some digging. I used OpenSSL to generate a key using the following commands:
openssl ecparam -genkey -name secp256r1 -out key.pem
openssl ec -in key.pem -text -noout
Private-Key: (256 bit)
priv:
1d:41:5f:23:2d:af:51:1b:e0:7c:88:5e:fe:1b:d2:
ad:e0:89:c0:fe:05:ff:93:1e:4c:a6:9e:f9:79:13:
ba:b0
pub:
04:31:e6:8e:38:9d:e5:ad:15:01:d1:ac:53:1a:f2:
a7:15:f9:0e:c8:6d:46:6d:69:73:94:b0:50:79:a0:
02:f2:a8:15:86:0c:92:64:36:ae:4f:42:40:63:ef:
9c:12:c4:85:07:23:b2:1b:bd:6a:d7:4e:2d:53:f1:
aa:86:7e:3a:be
ASN1 OID: prime256v1
NIST CURVE: P-256
I created a python script to call these commands and print the private and public keys as hex strings that Connect IQ understands, such as: [0x1d, 0x41, 0x5f, 0x23, 0x2d, 0xaf, 0x51, 0x1b, 0xe0, 0x7c, 0x88, 0x5e, 0xfe, 0x1b, 0xd2, 0xad, 0xe0, 0x89, 0xc0, 0xfe, 0x05, 0xff, 0x93, 0x1e, 0x4c, 0xa6, 0x9e, 0xf9, 0x79, 0x13, 0xba, 0xb0]b
for the private key above. I then pasted these into a simple test case in Connect IQ, shown below: (note, the leading 0x04 of the public key from OpenSSL above is removed because it denotes an uncompressed key, which Connect IQ doesn't seem to care about/distinguish)
(:test)
function test_key_generation(logger) {
var expected = [0x31, 0xe6, 0x8e, 0x38, 0x9d, 0xe5, 0xad, 0x15, 0x01, 0xd1, 0xac, 0x53, 0x1a, 0xf2, 0xa7, 0x15, 0xf9, 0x0e, 0xc8, 0x6d, 0x46, 0x6d, 0x69, 0x73, 0x94, 0xb0, 0x50, 0x79, 0xa0, 0x02, 0xf2, 0xa8, 0x15, 0x86, 0x0c, 0x92, 0x64, 0x36, 0xae, 0x4f, 0x42, 0x40, 0x63, 0xef, 0x9c, 0x12, 0xc4, 0x85, 0x07, 0x23, 0xb2, 0x1b, 0xbd, 0x6a, 0xd7, 0x4e, 0x2d, 0x53, 0xf1, 0xaa, 0x86, 0x7e, 0x3a, 0xbe]b;
var privKey = [0x1d, 0x41, 0x5f, 0x23, 0x2d, 0xaf, 0x51, 0x1b, 0xe0, 0x7c, 0x88, 0x5e, 0xfe, 0x1b, 0xd2, 0xad, 0xe0, 0x89, 0xc0, 0xfe, 0x05, 0xff, 0x93, 0x1e, 0x4c, 0xa6, 0x9e, 0xf9, 0x79, 0x13, 0xba, 0xb0]b;
var keyPair = new Crypto.KeyPair({:algorithm => Crypto.KEY_PAIR_ELLIPTIC_CURVE_SECP256R1, :privateKey => privKey});
logger.debug("Public key: " + hexString(keyPair.getPublicKey().getBytes(), 1));
return keyPair.getPublicKey().getBytes().equals(expected);
}
And it fails to generate the expected key. I went a little further, and used the pycryptodome library in python to test and see if the key that was generated by Connect IQ was valid:
from Crypto.PublicKey import ECC
# input format is '0xde, 0xad, 0xbe, 0xef...'
# or '0xde 0xad 0xbe 0xef...', either is okay
keystr = input('public key = ').replace(',', '')
keyarr = [entry[2:] for entry in keystr.split(' ')]
print(len(keyarr))
xstr = ''.join(keyarr[:32])
x = int(xstr, 16)
ystr = ''.join(keyarr[32:])
y = int(ystr, 16)
try:
key = ECC.construct(curve='p256', point_x=x, point_y=y)
print('Key successfully parsed')
except Exception as e:
print('Error parsing key:')
print(e)
There's no issue when I run this script with keys generated by OpenSSL, but when using the keys output from Connect IQ, I get the error:
Error parsing key: The EC point does not belong to the curve
That is interesting, to say the least. I tried it with the keys that I get back from the device, and those also work fine in the script.
[TL;DR]
The cryptography module isn't producing the expected output.
Do "FIPS P-256 Elliptic Curve" and "256-bit secp256r1 Elliptic Curve" actually refer to two completely different algorithms? Is my implementation/usage of the Cryptography module incorrect? Or is this a bug in the latest Connect IQ SDK?
Thanks