Skip to content

Commit 1012c87

Browse files
committed
Release 1.4.0
1 parent fd56eba commit 1012c87

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+631
-288
lines changed

Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ let package = Package(
1414
],
1515
dependencies: [
1616
// Dependencies declare other packages that this package depends on.
17-
.package(url: "https://github.com/leif-ibsen/BigInt", from: "1.13.0"),
17+
.package(url: "https://github.com/leif-ibsen/BigInt", from: "1.14.0"),
1818
.package(url: "https://github.com/leif-ibsen/ASN1", from: "2.1.0"),
1919
],
2020
targets: [

README.md

+34-15
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<li><a href="#basic6">Encryption and Decryption</a></li>
99
<li><a href="#basic7">Secret Export</a></li>
1010
<li><a href="#basic8">CryptoKit Compatibility</a></li>
11+
<li><a href="#basic9">Performance</a></li>
1112
<li><a href="#dep">Dependencies</a></li>
1213
<li><a href="#ref">References</a></li>
1314
</ul>
@@ -16,7 +17,7 @@ SwiftHPKE implements the Hybrid Public Key Encryption standard as defined in RFC
1617
In your project Package.swift file add a dependency like<br/>
1718

1819
dependencies: [
19-
.package(url: "https://github.com/leif-ibsen/SwiftHPKE", from: "1.3.0"),
20+
.package(url: "https://github.com/leif-ibsen/SwiftHPKE", from: "1.4.0"),
2021
]
2122
SwiftHPKE requires Swift 5.0. It also requires that the Int and UInt types be 64 bit types.
2223
SwiftHPKE uses Apple's CryptoKit framework. Therefore, for macOS the version must be at least 10.15,
@@ -246,33 +247,51 @@ is also possible.
246247
The SwiftHPKE keys of type .P256, .P384, .P521 and .X25519 are equivalent to
247248
CryptoKit keys of type P256, P384, P521 and Curve25519. Keys of type .X448 is not supported in CryptoKit.
248249

249-
To convert CryptoKit P256 keys (similarly for P384 and P521) - say *cc256priv* and *cc256pub*:
250+
To convert CryptoKit P256 keys (similarly for P384 and P521) - say *ckPriv* and *ckPub* to SwiftHPKE keys:
250251

251-
let hpke256priv = try PrivateKey(der: Bytes(cc256priv.derRepresentation))
252-
let hpke256pub = try PublicKey(der: Bytes(cc256pub.derRepresentation))
252+
let hpkePriv = try PrivateKey(der: Bytes(ckPriv.derRepresentation))
253+
let hpkePub = try PublicKey(der: Bytes(ckPub.derRepresentation))
253254

254-
To convert CryptoKit Curve25519 keys - say *cc25519priv* and *cc25519pub*:
255+
To convert CryptoKit Curve25519 keys - say *ckPriv* and *ckPub* to SwiftHPKE keys:
255256

256-
let hpke25519priv = try PrivateKey(kem: .X25519, bytes: Bytes(cc25519priv.rawRepresentation))
257-
let hpke25519pub = try PublicKey(kem: .X25519, bytes: Bytes(cc25519pub.rawRepresentation))
257+
let hpkePriv = try PrivateKey(kem: .X25519, bytes: Bytes(ckPriv.rawRepresentation))
258+
let hpkePub = try PublicKey(kem: .X25519, bytes: Bytes(ckPub.rawRepresentation))
258259

259-
To convert SwiftHPKE .P256 keys (similarly for .P384 and .P521) - say *hpke256priv* and *hpke256pub*:
260+
To convert SwiftHPKE .P256 keys (similarly for .P384 and .P521) - say *hpkePriv* and *hpkePub* to CryptoKit keys:
260261

261-
let cc256priv = try CryptoKit.P256.KeyAgreement.PrivateKey(derRepresentation: hpke256priv.der)
262-
let cc256pub = try CryptoKit.P256.KeyAgreement.PublicKey(derRepresentation: hpke256pub.der)
262+
let ckPriv = try CryptoKit.P256.KeyAgreement.PrivateKey(derRepresentation: hpkePriv.der)
263+
let ckPub = try CryptoKit.P256.KeyAgreement.PublicKey(derRepresentation: hpkePub.der)
263264

264-
To convert SwiftHPKE .X25519 keys - say *hpke25519priv* and *hpke25519pub*:
265+
To convert SwiftHPKE .X25519 keys - say *hpkePriv* and *hpkePub* to CryptoKit keys:
265266

266-
let cc25519priv = try CryptoKit.Curve25519.KeyAgreement.PrivateKey(rawRepresentation: hpke25519priv.bytes)
267-
let cc25519pub = try CryptoKit.Curve25519.KeyAgreement.PublicKey(rawRepresentation: hpke25519pub.bytes)
267+
let ckPriv = try CryptoKit.Curve25519.KeyAgreement.PrivateKey(rawRepresentation: hpkePriv.bytes)
268+
let ckPub = try CryptoKit.Curve25519.KeyAgreement.PublicKey(rawRepresentation: hpkePub.bytes)
268269

269-
<h2 id="dep"><b>Dependencies</b></h2>
270+
<h2 id="basic9"><b>Performance</b></h2>
271+
SwiftHPKE's encryption and decryption performance was measured on an iMac 2021, Apple M1 chip.
272+
The time to create a *Sender* and *Recipient* instance in base mode is shown in the table below, depending on the KEM type - units are milliseconds.
273+
<table width="90%">
274+
<tr><th align="left" width="16%">KEM</th><th align="right" width="28%">Sender</th><th align="right" width="28%">Recipient</th></tr>
275+
<tr><td>P256</td><td align="right">7 mSec</td><td align="right">6 mSec</td></tr>
276+
<tr><td>P384</td><td align="right">20 mSec</td><td align="right">17 mSec</td></tr>
277+
<tr><td>P521</td><td align="right">46 mSec</td><td align="right">39 mSec</td></tr>
278+
<tr><td>X25519</td><td align="right">0.14 mSec</td><td align="right">0.09 mSec</td></tr>
279+
<tr><td>X448</td><td align="right">1.1 mSec</td><td align="right">0.5 mSec</td></tr>
280+
</table>
281+
The encryption and decryption speed in base mode, once the *Sender* or *Recipient* instance is created, is shown in the table below, depending on the AEAD type - units are MBytes / Sec.
282+
<table width="90%">
283+
<tr><th align="left" width="16%">AEAD</th><th align="right" width="28%">Encryption speed</th><th align="right" width="28%">Decryption speed</th></tr>
284+
<tr><td>AESGCM128</td><td align="right">3500 MB/Sec (0.91 cycles / byte)</td><td align="right">3340 MB/Sec (0.96 cycles / byte)</td></tr>
285+
<tr><td>AESGCM256</td><td align="right">3640 MB/Sec (0.88 cycles / byte)</td><td align="right">3630 MB/Sec (0.88 cycles / byte)</td></tr>
286+
<tr><td>CHACHAPOLY</td><td align="right">555 MB/Sec (5.8 cycles / byte)</td><td align="right">557 MB/Sec (5.7 cycles / byte)</td></tr>
287+
</table>
270288

289+
<h2 id="dep"><b>Dependencies</b></h2>
271290
The SwiftHPKE package depends on the ASN1 and BigInt packages
272291

273292
dependencies: [
274293
.package(url: "https://github.com/leif-ibsen/ASN1", from: "2.1.0"),
275-
.package(url: "https://github.com/leif-ibsen/BigInt", from: "1.13.0"),
294+
.package(url: "https://github.com/leif-ibsen/BigInt", from: "1.14.0"),
276295
],
277296

278297
<h2 id="ref"><b>References</b></h2>

Sources/SwiftHPKE/CipherSuite.swift

-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ public typealias Byte = UInt8
1010
/// Array of unsigned 8 bit values
1111
public typealias Bytes = [UInt8]
1212

13-
14-
/// A HPKE CipherSuite
15-
///
1613
/// A CipherSuite instance combines a *Key Encapsulation Mechanism* (KEM), a *Key Derivation Function* (KDF)
1714
/// and a *AEAD Encryption Algorithm* (AEAD).
1815
/// It can encrypt or decrypt a single message in one of four modes:

Sources/SwiftHPKE/PrivateKey.swift

-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
import ASN1
99
import BigInt
1010

11-
/// A HPKE private key
12-
///
1311
/// There are five different private key types corresponding to the five KEM's
1412
///
1513
/// * P256 - the key is a 32 byte value corresponding to a NIST curve secp256r1 private key

Sources/SwiftHPKE/PublicKey.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
import ASN1
99
import BigInt
1010

11-
/// A HPKE public key
12-
///
1311
/// There are five different public key types corresponding to the five KEM's
1412
///
1513
/// * P256 - the key is a 65 byte value corresponding to a NIST secp256r1 uncompressed curve point
@@ -26,7 +24,9 @@ public struct PublicKey: CustomStringConvertible, Equatable {
2624

2725
// MARK: Initializers
2826

29-
/// Creates a PublicKey from its type and key bytes
27+
/// Creates a PublicKey from its type and key bytes.<br/>
28+
/// For types P256, P384 and P521 the key bytes represents
29+
/// either a compressed curve point or an uncompressed curve point.
3030
///
3131
/// - Parameters:
3232
/// - kem: The key type

Sources/SwiftHPKE/Recipient.swift

+2-3
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@
55
// Created by Leif Ibsen on 19/06/2023.
66
//
77

8-
/// Recipient
9-
///
108
/// Based on its *CipherSuite*, a *Recipient* instance can decrypt a sequence of messages in one of four modes:
119
///
1210
/// * Base mode
1311
/// * Preshared key mode
1412
/// * Authenticated mode
1513
/// * Authenticated, preshared key mode
1614
///
17-
/// The decryption of the messages must be done in the order in which they were encrypted
15+
/// The decryption of the messages must be done in the order in which they were encrypted.<br/>
16+
/// A *Recipient* instance can also retrieve a generated export secret.
1817
public class Recipient {
1918

2019
let suite: CipherSuite

Sources/SwiftHPKE/Sender.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
// Created by Leif Ibsen on 19/06/2023.
66
//
77

8-
/// Sender
9-
///
108
/// Based on its *CipherSuite*, a *Sender* instance can encrypt a sequence of messages in one of four modes:
119
/// * Base mode
1210
/// * Preshared key mode
1311
/// * Authenticated mode
1412
/// * Authenticated, preshared key mode
13+
///
14+
/// A *Sender* instance can also generate an export secret that only the recipient can know.
1515
public class Sender {
1616

1717
let suite: CipherSuite

Sources/SwiftHPKE/X255_448/Field25519.swift

+12-12
Original file line numberDiff line numberDiff line change
@@ -140,23 +140,23 @@ struct Field25519: CustomStringConvertible {
140140
}
141141

142142
func add(_ x: Field25519) -> Field25519 {
143-
var v = Field25519(
144-
self.l0 + x.l0,
145-
self.l1 + x.l1,
146-
self.l2 + x.l2,
147-
self.l3 + x.l3,
148-
self.l4 + x.l4)
143+
var v = self
144+
v.l0 += x.l0
145+
v.l1 += x.l1
146+
v.l2 += x.l2
147+
v.l3 += x.l3
148+
v.l4 += x.l4
149149
v.carryPropagate()
150150
return v
151151
}
152152

153153
func sub(_ x: Field25519) -> Field25519 {
154-
var v = Field25519(
155-
(self.l0 + 0xfffffffffffda) - x.l0,
156-
(self.l1 + 0xffffffffffffe) - x.l1,
157-
(self.l2 + 0xffffffffffffe) - x.l2,
158-
(self.l3 + 0xffffffffffffe) - x.l3,
159-
(self.l4 + 0xffffffffffffe) - x.l4)
154+
var v = self
155+
v.l0 += 0xfffffffffffda - x.l0
156+
v.l1 += 0xffffffffffffe - x.l1
157+
v.l2 += 0xffffffffffffe - x.l2
158+
v.l3 += 0xffffffffffffe - x.l3
159+
v.l4 += 0xffffffffffffe - x.l4
160160
v.carryPropagate()
161161
return v
162162
}

Sources/SwiftHPKE/X255_448/Field448.swift

+8-3
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,14 @@ struct Field448: CustomStringConvertible {
8484

8585
func add(_ a: Field448) -> Field448 {
8686
var x = self
87-
for i in 0 ..< 8 {
88-
x.l[i] &+= a.l[i]
89-
}
87+
x.l[0] &+= a.l[0]
88+
x.l[1] &+= a.l[1]
89+
x.l[2] &+= a.l[2]
90+
x.l[3] &+= a.l[3]
91+
x.l[4] &+= a.l[4]
92+
x.l[5] &+= a.l[5]
93+
x.l[6] &+= a.l[6]
94+
x.l[7] &+= a.l[7]
9095
x.reduce()
9196
return x
9297
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//
2+
// WycheproofP256Test.swift
3+
//
4+
//
5+
// Created by Leif Ibsen on 06/09/2023.
6+
//
7+
8+
import XCTest
9+
@testable import SwiftHPKE
10+
11+
// Test vectors from project Wycheproof - ecdh_secp256r1_test.json
12+
final class WycheproofP256Test: XCTestCase {
13+
14+
static func hex2bytes(_ x: String) -> Bytes {
15+
let b = [Byte](x.utf8)
16+
var bytes = Bytes(repeating: 0, count: b.count / 2)
17+
for i in 0 ..< bytes.count {
18+
let b0 = b[2 * i]
19+
let b1 = b[2 * i + 1]
20+
bytes[i] = ((b0 > 57 ? b0 - 97 + 10 : b0 - 48) << 4) | (b1 > 57 ? b1 - 97 + 10 : b1 - 48)
21+
}
22+
return bytes
23+
}
24+
25+
struct dhTest {
26+
27+
let pubKey: Bytes
28+
let privKey: Bytes
29+
let shared: Bytes
30+
31+
init(_ pubKey: String, _ privKey: String, _ shared: String) {
32+
self.pubKey = hex2bytes(pubKey)
33+
self.privKey = hex2bytes(privKey)
34+
self.shared = hex2bytes(shared)
35+
}
36+
}
37+
38+
let tests256: [dhTest] = [
39+
// tcId = 1, normal case
40+
dhTest(
41+
"0462d5bd3372af75fe85a040715d0f502428e07046868b0bfdfa61d731afe44f26ac333a93a9e70a81cd5a95b5bf8d13990eb741c8c38872b4a07d275a014e30cf",
42+
"0612465c89a023ab17855b0a6bcebfd3febb53aef84138647b5352e02c10c346",
43+
"53020d908b0219328b658b525f26780e3ae12bcd952bb25a93bc0895e1714285"),
44+
// tcId = 2, compressed public key
45+
dhTest(
46+
"0362d5bd3372af75fe85a040715d0f502428e07046868b0bfdfa61d731afe44f26",
47+
"0612465c89a023ab17855b0a6bcebfd3febb53aef84138647b5352e02c10c346",
48+
"53020d908b0219328b658b525f26780e3ae12bcd952bb25a93bc0895e1714285"),
49+
// tcId = 3, shared secret has x-coordinate that satisfies x**2 = 0
50+
dhTest(
51+
"0458fd4168a87795603e2b04390285bdca6e57de6027fe211dd9d25e2212d29e62080d36bd224d7405509295eed02a17150e03b314f96da37445b0d1d29377d12c",
52+
"0a0d622a47e48f6bc1038ace438c6f528aa00ad2bd1da5f13ee46bf5f633d71a",
53+
"0000000000000000000000000000000000000000000000000000000000000000"),
54+
// tcId = 4, shared secret has x-coordinate p-3
55+
dhTest(
56+
"04a1ecc24bf0d0053d23f5fd80ddf1735a1925039dc1176c581a7e795163c8b9ba2cb5a4e4d5109f4527575e3137b83d79a9bcb3faeff90d2aca2bed71bb523e7e",
57+
"0a0d622a47e48f6bc1038ace438c6f528aa00ad2bd1da5f13ee46bf5f633d71a",
58+
"ffffffff00000001000000000000000000000000fffffffffffffffffffffffc"),
59+
// tcId = 45, y-coordinate of the public key is small
60+
dhTest(
61+
"043cbc1b31b43f17dc200dd70c2944c04c6cb1b082820c234a300b05b7763844c74fde0a4ef93887469793270eb2ff148287da9265b0334f9e2609aac16e8ad503",
62+
"0a0d622a47e48f6bc1038ace438c6f528aa00ad2bd1da5f13ee46bf5f633d71a",
63+
"7fffffffffffffffffffffffeecf2230ffffffffffffffffffffffffffffffff"),
64+
// tcId = 51, y-coordinate of the public key is large
65+
dhTest(
66+
"043cbc1b31b43f17dc200dd70c2944c04c6cb1b082820c234a300b05b7763844c7b021f5b006c778ba686cd8f14d00eb7d78256d9b4fccb061d9f6553e91752afc",
67+
"0a0d622a47e48f6bc1038ace438c6f528aa00ad2bd1da5f13ee46bf5f633d71a",
68+
"7fffffffffffffffffffffffeecf2230ffffffffffffffffffffffffffffffff"),
69+
// tcId = 228, point with coordinate y = 1
70+
dhTest(
71+
"0409e78d4ef60d05f750f6636209092bc43cbdd6b47e11a9de20a9feb2a50bb96c0000000000000000000000000000000000000000000000000000000000000001",
72+
"00809c461d8b39163537ff8f5ef5b977e4cdb980e70e38a7ee0b37cc876729e9ff",
73+
"28f67757acc28b1684ba76ffd534aed42d45b8b3f10b82a5699416eff7199a74"),
74+
]
75+
76+
func test256() throws {
77+
let kem = KEMStructure(.P256)
78+
for test in tests256 {
79+
let priv = try PrivateKey(kem: .P256, bytes: test.privKey)
80+
let pub = try PublicKey(kem: .P256, bytes: test.pubKey)
81+
XCTAssertEqual(try kem.DH(priv, pub), test.shared)
82+
}
83+
}
84+
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// WycheproofP384Test.swift
3+
//
4+
//
5+
// Created by Leif Ibsen on 06/09/2023.
6+
//
7+
8+
import XCTest
9+
@testable import SwiftHPKE
10+
11+
// Test vectors from project Wycheproof - ecdh_secp384r1_test.json
12+
final class WycheproofP384Test: XCTestCase {
13+
14+
static func hex2bytes(_ x: String) -> Bytes {
15+
let b = [Byte](x.utf8)
16+
var bytes = Bytes(repeating: 0, count: b.count / 2)
17+
for i in 0 ..< bytes.count {
18+
let b0 = b[2 * i]
19+
let b1 = b[2 * i + 1]
20+
bytes[i] = ((b0 > 57 ? b0 - 97 + 10 : b0 - 48) << 4) | (b1 > 57 ? b1 - 97 + 10 : b1 - 48)
21+
}
22+
return bytes
23+
}
24+
25+
struct dhTest {
26+
27+
let pubKey: Bytes
28+
let privKey: Bytes
29+
let shared: Bytes
30+
31+
init(_ pubKey: String, _ privKey: String, _ shared: String) {
32+
self.pubKey = hex2bytes(pubKey)
33+
self.privKey = hex2bytes(privKey)
34+
self.shared = hex2bytes(shared)
35+
}
36+
}
37+
38+
let tests384: [dhTest] = [
39+
// tcId = 1, normal case
40+
dhTest(
41+
"04790a6e059ef9a5940163183d4a7809135d29791643fc43a2f17ee8bf677ab84f791b64a6be15969ffa012dd9185d8796d9b954baa8a75e82df711b3b56eadff6b0f668c3b26b4b1aeb308a1fcc1c680d329a6705025f1c98a0b5e5bfcb163caa",
42+
"766e61425b2da9f846c09fc3564b93a6f8603b7392c785165bf20da948c49fd1fb1dee4edd64356b9f21c588b75dfd81",
43+
"6461defb95d996b24296f5a1832b34db05ed031114fbe7d98d098f93859866e4de1e229da71fef0c77fe49b249190135"),
44+
// tcId = 2, compressed public key
45+
dhTest(
46+
"02790a6e059ef9a5940163183d4a7809135d29791643fc43a2f17ee8bf677ab84f791b64a6be15969ffa012dd9185d8796",
47+
"766e61425b2da9f846c09fc3564b93a6f8603b7392c785165bf20da948c49fd1fb1dee4edd64356b9f21c588b75dfd81",
48+
"6461defb95d996b24296f5a1832b34db05ed031114fbe7d98d098f93859866e4de1e229da71fef0c77fe49b249190135"),
49+
// tcId = 3, shared secret has x-coordinate that satisfies x**2 = 0
50+
dhTest(
51+
"04490e96d17f4c6ceccd45def408cea33e9704a5f1b01a3de2eaaa3409fd160d78d395d6b3b003d71fd1f590fad95bf1c9d8665efc2070d059aa847125c2f707435955535c7c5df6d6c079ec806dce6b6849d337140db7ca50616f9456de1323c4",
52+
"00a2b6442a37f8a3759d2cb91df5eca75b14f5a6766da8035cc1943b15a8e4ebb6025f373be334080f22ab821a3535a6a7",
53+
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
54+
// tcId = 49, y-coordinate of the public key is small
55+
dhTest(
56+
"04bfeb47fb40a65878e6b642f40b8e15022ade9ecfa8cb618043063494e2bc5d2df10d36f37869b58ef12dcc35e3982835fd2e55ec41fdfe8cabbbb7bcd8163645a19e9dac59630f3fe93b208094ff87cd461b53cef53482e70e2e8ea87200cc3f",
57+
"00a2b6442a37f8a3759d2cb91df5eca75b14f5a6766da8035cc1943b15a8e4ebb6025f373be334080f22ab821a3535a6a7",
58+
"0000000000000000000000000000000000000000000000000000000036a2907c00000000000000000000000000000000"),
59+
// tcId = 51, y-coordinate of the public key is large
60+
dhTest(
61+
"04bfeb47fb40a65878e6b642f40b8e15022ade9ecfa8cb618043063494e2bc5d2df10d36f37869b58ef12dcc35e398283502d1aa13be0201735444484327e9c9ba5e616253a69cf0c016c4df7f6b007831b9e4ac300acb7d18f1d171588dff33c0",
62+
"00a2b6442a37f8a3759d2cb91df5eca75b14f5a6766da8035cc1943b15a8e4ebb6025f373be334080f22ab821a3535a6a7",
63+
"0000000000000000000000000000000000000000000000000000000036a2907c00000000000000000000000000000000"),
64+
// tcId = 585, point with coordinate y = 1
65+
dhTest(
66+
"042261b2bf605c22f2f3aef6338719b2c486388ad5240719a5257315969ef01ba27f0a104c89704773a81fdabee6ab5c78000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
67+
"00c1781d86cac2c052b7e4f48cef415c5c133052f4e504397e75e4d7cd0ca149da0b4988b8a6ded5ceae4b580691376187",
68+
"c923fb0d4b24e996e5e0d5df151d3c26b1f61c05b17b7fb39fc8590b47eeaff34709f6f7328923bdcaf7e8e413d77ddc"),
69+
]
70+
71+
func test384() throws {
72+
let kem = KEMStructure(.P384)
73+
for test in tests384 {
74+
let priv = try PrivateKey(kem: .P384, bytes: test.privKey)
75+
let pub = try PublicKey(kem: .P384, bytes: test.pubKey)
76+
XCTAssertEqual(try kem.DH(priv, pub), test.shared)
77+
}
78+
}
79+
80+
}

0 commit comments

Comments
 (0)