1import Crypto
2import Foundation
3
4struct KeyFormatError: Error {}
5struct WeakPeerKeyError: Error {}
6
7/// X25519 keys have only a raw form; RFC 8410 fixes the DER around the 32 bytes.
8func rawX25519Key(pemFile: String, derPrefix: [UInt8]) throws -> Data {
9 let body = try String(contentsOfFile: pemFile, encoding: .utf8)
10 .split(separator: "\n").filter { !$0.hasPrefix("-----") }.joined()
11 guard let der = Data(base64Encoded: body), der.count == derPrefix.count + 32, der.starts(with: derPrefix) else {
12 throw KeyFormatError()
13 }
14 return der.suffix(32)
15}
16
17let x25519PKCS8Prefix: [UInt8] = [0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x04, 0x22, 0x04, 0x20]
18let x25519SPKIPrefix: [UInt8] = [0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00]
19
20let aliceX25519 = try Curve25519.KeyAgreement.PrivateKey(
21 rawRepresentation: rawX25519Key(pemFile: "alice-x25519.pem", derPrefix: x25519PKCS8Prefix))
22let bobX25519 = try Curve25519.KeyAgreement.PublicKey(
23 rawRepresentation: rawX25519Key(pemFile: "bob-x25519.pub.pem", derPrefix: x25519SPKIPrefix))
24
25// P-256 keys read PEM directly
26let aliceP256 = try P256.KeyAgreement.PrivateKey(pemRepresentation: String(contentsOfFile: "alice-p256.pem", encoding: .utf8))
27let bobP256 = try P256.KeyAgreement.PublicKey(pemRepresentation: String(contentsOfFile: "bob-p256.pub.pem", encoding: .utf8))
28
29// swift-crypto accepts a low-order X25519 peer key and returns 32 zero bytes: reject that
30let x25519Secret = try aliceX25519.sharedSecretFromKeyAgreement(with: bobX25519)
31guard !x25519Secret.withUnsafeBytes({ $0.allSatisfy { $0 == 0 } }) else { throw WeakPeerKeyError() }
32
33let secrets = [
34 ("X25519", x25519Secret),
35 ("P-256", try aliceP256.sharedSecretFromKeyAgreement(with: bobP256)),
36]
37for (curve, secret) in secrets {
38 let key = secret.hkdfDerivedSymmetricKey(
39 using: SHA256.self, salt: Data(), sharedInfo: Data("AES-256-GCM key v1".utf8), outputByteCount: 32)
40 // Printing secrets is for this demonstration only
41 let secretHex = secret.withUnsafeBytes { $0.map { String(format: "%02x", $0) }.joined() }
42 let keyHex = key.withUnsafeBytes { $0.map { String(format: "%02x", $0) }.joined() }
43 print("\(curve) shared secret: \(secretHex)")
44 print("\(curve) derived key: \(keyHex)")
45}