I am trying to send encrypted messages using AES-GCM between an iOS app and an ESP microcontroller. But before I do that, I need to perform a key exchange between the two devices so that they both have a shared secret. So I am looking into iOS methods for generating private/public key pairs and there are several. However there is one problem, the keys generated by iOS aren't readily compatible with anything outside of iOS (as far as I understand) which means I have to jump in and make some modifications to the keys. I noticed the keys that are generated are always abdc1234 where abcd never changes and 1234 changes every time a key is generated. So I am assuming that abcd is just something iOS uses and 1234 is the actual key. At least, that's what happens in Android. So to test my hypothesis, I'm trying to get the raw bytes of the public key that was generated in iOS so I can cut off the abcd and send the actual key 1234 to the ESP. The problem is I can't find how to access the bytes that the key contains. Here's what I tried so far...
let tag = "".data(using: .utf8)!
let attributes: [String: Any] = [
kSecAttrType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: tag
]
]
let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error)
let publicKey = SecKeyCopyPublicKey(privateKey!)
print("\(publicKey!)")
var address = Unmanaged.passUnretained(publicKey!).toOpaque()
do{
withUnsafeBytes(of: &address) { bytes in
for byte in bytes{
print(byte)
}
}
}
So the first print statement outputs the following
<SecKeyRef curve type: kSecECCurveSecp256r1, algorithm id: 3, key type: ECPublicKey, version: 4, block size: 256, bits, y: ..., x: ..., addr: 0x...>
Which so far so good, the key that I want is on the curve that I want and the key is the correct length and such.
But now the next print outputs the following
144
70
215
9
1
0
0
0
That's it. Clearly, the last 8 things to be printed are not the key, it's too short. So yeah, how do I extract the x and y value of the public key. I can see it printed so there must be a way to access it but yeah I have searched everywhere and no dice. My theory that I can "chop" off the "iOS" part of the generated key might not even be correct but the thing is I can't even test it without being able to send the bytes to the ESP. I hope there is an easier way to achieve the key exchange between iOS app and ESP but for now, this is the only way I know how. Oh yeah, I send the key bytes over bluetooth, I was able to connect the iOS app to the ESP via bluetooth and that's how I'm trying to make the key exchange. I know bluetooth is techncially encrypted but I just want to do the key exchange and then encrypt the bluetooth messages further by using AES-GCM. So please. If you know of a way to access the key bytes, please share!
UPDATE:
I use this code to generate key pair for ECDH key exchange:
let attributes: [String: Any] = [
kSecAttrType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256,
kSecPrivateKeysAttrs as String: [
kSecAttrIsPermanent as String: true
]
]
var error: Unmanaged<CFError>?
privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error)!
publicKey = SecKeyCopyPublicKey(privateKey!)!
let pubKeyExternRep = SecKeyCopyExternalRepresentation(publicKey!, &error)
print("\(publicKeyExternRep!)")
Which outputs the following:
{length = 65, bytes = 0x048820d8 0482e62f 7abac673 02d8a68e ... 6e0117684
ff455540 }
I am trying to get everything after the 0x04 in the bytes section to be in a character array so that I can send it over bluetooth in a sequence of packets. So it is imperative that I obtain everything after the 0x04, all 128 characters, and have it in a byte array. In the end, if I print the contents of the byte array, it should just say 8820d80482e62f ... 6e0117684ff455540.
I have tried the following:
//^^^ previous code block that generates the key pair ^^^
let length = CFDataGetLength(pubKeyExternRep!)
let range = CFRange.init(location: 0, length: length)
let uint8Pointer = UnsafeMutablePointer<UInt8>.allocate(capacity:length)
CFDataGetBytes(pubKeyExternRep!, range, uint8Pointer)
print("\(uint8Pointer.pointee)")
This outputs the following:
4
Which seems promising since it matches the first 4 of the key, the 0x04 but then I move up the pointer by switching location: 5 and it prints a random two digit number that doesn't match with anything useful, i.e. 68 or something like that.
I also tried this:
^^^ previous code block that generates the key pair ^^^
let keyData = pubKeyExternRep as Data?
let dataString = keyData?.base64EncodedString()
var byteArray: [UInt8] = []
byteArray = Array(dataString!.utf8)
print("\(byteArray)")
Now this gets me extremely close to what I need, it's a byte array and I can perform operations on it and concatenate stuff and such, if I print it outputs this:
[66, 79, 76, ..., 119, 81, 61]
The problem is not only do the members of the array not match any of the 128 bytes in the pubKeyExternRep but its also shorter than the 128 bytes I need. It's 64 bytes. This is actually something I been noticing for both methods I tried (the CFDataGetBytes method and the String to Array method), whatever it is I get, it's never 128 bytes, it's always 64.
I just need all 128 bytes after the 0x04 when I print(publicKeyExternRep!)
UPDATE 2:
Found solution, it's not the one that's marked but that did technically answer the question. Will post solution after work!
Just use SecKeyCopyExternalRepresentation(_:_:). Encoded format is not as what is commonly referred to as X9.63 by the way, it is a flat uncompressed public key point without the parameters of the named curve.
Related
I want to hash a message with the RSA class using f#.
Currently, I managed to create these few lines using the default randomly generated key pair.
let rsa = RSA.Create()
let dataInBytes = System.Text.Encoding.UTF8.GetBytes ("Message to be hashed.")
let bytes = rsa.SignData(dataInBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
In order to use my own private/public key pair I want to use .ImportPkcs8PrivateKey(). My code is:
let privKey = System.IO.File.ReadAllText #"C:\Users\User\Documents\FSharp\privkey.pem";;
val privKey: string =
"-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASC"+[1671 chars]
let convertFromBase64ToBytes (input: string) =
let str = match input.Length % 4 with
| 1 -> input.Substring (1, input.Length - 1)
| 2 -> input + string "=="
| 3 -> input + string "="
| _ -> input
let strReplaced = str
.Replace("-", "+")
.Replace("_", "/")
System.Convert.FromBase64String strReplaced
let privKeyBytes = convertFromBase64ToBytes privKey
> rsa.ImportPkcs8PrivateKey (privKeyBytes);;
but it throws me the error:
System.Security.Cryptography.CryptographicException: ASN1 corrupted data.
---> System.Formats.Asn1.AsnContentException: The encoded length exceeds the maximum supported by this library (Int32.MaxValue).
at System.Formats.Asn1.AsnDecoder.ReadLength(ReadOnlySpan`1 source, AsnEncodingRules ruleSet, Int32& bytesConsumed)
at System.Formats.Asn1.AsnDecoder.ReadEncodedValue(ReadOnlySpan`1 source, AsnEncodingRules ruleSet, Int32& contentOffset, Int32& contentLength, Int32& bytesConsumed)
at System.Security.Cryptography.CngPkcs8.ImportPkcs8PrivateKey(ReadOnlySpan`1 source, Int32& bytesRead)
--- End of inner exception stack trace ---
at System.Security.Cryptography.CngPkcs8.ImportPkcs8PrivateKey(ReadOnlySpan`1 source, Int32& bytesRead)
at System.Security.Cryptography.RSAImplementation.RSACng.ImportPkcs8PrivateKey(ReadOnlySpan`1 source, Int32& bytesRead)
at <StartupCode$FSI_0012>.$FSI_0012.main#() in C:\Users\User\PowerShell\stdin:line 26
Stopped due to error
What am I missing?
ImportPkcs8PrivateKey() expects a DER encoded key in PKCS#8 format. The posted key has the correct format, but is PEM encoded. The conversion from PEM to DER consists of removing the header, footer, all line breaks, and Base64 decoding the rest:
...
let derB64 = privKey.Replace("-----BEGIN PRIVATE KEY-----", "").Replace("-----END PRIVATE KEY-----", "").Replace("\r\n", "")
let privKeyBytes = Convert.FromBase64String derB64
rsa.ImportPkcs8PrivateKey privKeyBytes
...
As of .NET 5, the ImportFromPem() method is available, which can directly import the PEM encoded key.
The convertFromBase64ToBytes() method converts Base64url encoded data to Base64 encoded data and then performs a Base64 decoding.
Since PEM encoded keys are Base64 encoded (and not Base64url encoded) this method is actually not necessary, instead Convert.FromBase64String() can be applied directly (if it is used anyway, it has no effect, because already Base64 encoded data is not changed).
The format that begins / ends with -----BEGIN XXX----- and ends with a similar line is called PEM encoding.
You are missing the fact that the base 64 also contains newlines and - etc. (so the length calculation fails). You should not have to pad with = symbols - those are mandatory for PEM. Similarly, PEM uses normal base 64, so there is no need to do anything with - or _ characters, as those should not be present (in the base 64, i.e. outside the header / footer lines obviously). In short, the base 64 (url) decoding fails on multiple levels.
Probably best to use PEM specific encoding / decoding routines. You can find those here if you're using a new runtime, or otherwise you can use the Bouncy Castle libraries.
I'm writing a HomeKit (so perhaps Bluetooth) characteristic in TLV8 format. Apple doc says
The value is an NSData object containing a set of one or more TLV8's,
which are packed type-length-value items with an 8-bit type, 8-bit
length, and N-byte value.
According to Wikipeida a type-length value is
Type
A binary code, often simply alphanumeric, which indicates the kind of field that this part of the message represents;
Length
The size of the value field (typically in bytes);
Value
Variable-sized series of bytes which contains data for this part of the message.
I have no idea how to pack one. I suppose I can write raw bytes to NSData, but what do I write for pad, if I need any padding, etc. So is there an example of how to do that?
Oh I figured it out.
TLV8 consist of three sections: "Tag", "Length", and "Value". I don't know what 8 means.
Both tag and length are UInt8. I believe what the tag may be depend on where the TLV8 is used. Length is the length of the value. Value is the content it self.
So when I want to send a simple 1 as a value, I use:
let tag = 0x02 // For example
let length = 0x01
let value = 0x01
let data = Data(bytes: [tag, length, value]) // NSData
I have to send a byte array of hexadecimal integers to a BLE devise. Whenever I try to use 0x notation the internal value stored in the byte array is getting converted to a decimal value.
var foo : [Byte] = [0xff, 0xD9] is actually stored as [255, 217]
What data structure should I be using to make it stay in hex format ?
You keep bytes in a byte array and what do they mean is up for your interpretation. So what you are keeping there are bytes of value 255 and 217 in base10 and 0xFF and 0xD9 in base16. They are actually kept as 1111 1111 and 1101 1001 in base2. (Computer number format)
You can safely send your byte array without any changes.
If you want to print out your bytes in hexadecimal you can go with something like this:
println (foo.map { String($0, radix: 16, uppercase: false) })
I am using the following path in my spray-can server (using spray 1.2):
path("my"/"path"){
get{
complete{
val buf:Array[Byte] = functionReturningArrayofByte()
println(buf.length)
buf
}
}
}
The length of the buffer (and what is printed by the code above) is 2,263,503 bytes. However, when accessing my/path from a web browser, it downloads a file that is 10,528,063 bytes long.
I thought spray set the content type to application/octet-stream, and the content length, automatically when completing with an Array[Byte]. I don't realize what I may be doing wrong.
EDIT
I've run a small test and have seen that the array of bytes is output as a String. So, for example, if I had two bytes, for example 0xFF and 0x01, the output, instead of just the two bytes, would be the string [ 255, 1 ]. I just don't know how to make it output the raw content instead of a string representation of it.
Wrapping the buf as HttpData solves the problem:
path("my"/"path"){
get{
complete{
val buf:Array[Byte] = functionReturningArrayofByte()
HttpData(buf)
}
}
}
I'm trying to extract a 1024-bit RSA public key from an already generated key pair (two SecKeyRefs), in order to send it over the wire. All I need is a plain (modulus, exponent) pair, which should take up exactly 131 bytes (128 for the modulus and 3 for the exponent).
However, when I fetch the key info as a NSData object, I get 140 bits instead of 131. Here's an example result:
<30818902 818100d7 514f320d eacf48e1 eb64d8f9 4d212f77 10dd3b48 ba38c5a6
ed6ba693 35bb97f5 a53163eb b403727b 91c34fc8 cba51239 3ab04f97 dab37736
0377cdc3 417f68eb 9e351239 47c1f98f f4274e05 0d5ce1e9 e2071d1b 69a7cac4
4e258765 6c249077 dba22ae6 fc55f0cf 834f260a 14ac2e9f 070d17aa 1edd8db1
0cd7fd4c c2f0d302 03010001>
After retrying the key generation a couple of times and comparing the resulting NSData objects, the bytes that remain the same for all keys are the first 7:
<30818902 818100>
The last three bytes look like the exponent (65537, a common value). There are also two bytes between the "modulus" and the exponent:
<0203>
Can someone with more crypto experience help me identify what encoding is this? DER? How do I properly decode the modulus and exponent?
I tried manually stripping out the modulus and exponent using
NSData* modulus = [keyBits subdataWithRange:(NSRange){ 7, 128 }];
NSData* exponent = [keyBits subdataWithRange:(NSRange){ 7 + 128 + 2, 3 }];
but I get errors when trying to decrypt data which the remote host encoded using that "key".
EDIT:
Here's a gist of the solution I ended up using to unpack the RSA blob: https://gist.github.com/vl4dimir/6079882
Assuming you want the solution to work under iOS, please have a look at this thread. The post confirms that the encoding is DER and shows how to extract the exponent and modulus from the NSData object you started with.
There is another solution that won't work on iOS, but will work on Desktop systems (including MacOS X) that have OpenSSL installed in this thread. Even if you are looking for the iOS-only solution you can still use this to verify your code is working correctly.