Skip to content

Commit

Permalink
Release 1.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
leif-ibsen committed Oct 2, 2023
1 parent fd56eba commit 1012c87
Show file tree
Hide file tree
Showing 47 changed files with 631 additions and 288 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let package = Package(
],
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/leif-ibsen/BigInt", from: "1.13.0"),
.package(url: "https://github.com/leif-ibsen/BigInt", from: "1.14.0"),
.package(url: "https://github.com/leif-ibsen/ASN1", from: "2.1.0"),
],
targets: [
Expand Down
49 changes: 34 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<li><a href="#basic6">Encryption and Decryption</a></li>
<li><a href="#basic7">Secret Export</a></li>
<li><a href="#basic8">CryptoKit Compatibility</a></li>
<li><a href="#basic9">Performance</a></li>
<li><a href="#dep">Dependencies</a></li>
<li><a href="#ref">References</a></li>
</ul>
Expand All @@ -16,7 +17,7 @@ SwiftHPKE implements the Hybrid Public Key Encryption standard as defined in RFC
In your project Package.swift file add a dependency like<br/>

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

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

let hpke256priv = try PrivateKey(der: Bytes(cc256priv.derRepresentation))
let hpke256pub = try PublicKey(der: Bytes(cc256pub.derRepresentation))
let hpkePriv = try PrivateKey(der: Bytes(ckPriv.derRepresentation))
let hpkePub = try PublicKey(der: Bytes(ckPub.derRepresentation))

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

let hpke25519priv = try PrivateKey(kem: .X25519, bytes: Bytes(cc25519priv.rawRepresentation))
let hpke25519pub = try PublicKey(kem: .X25519, bytes: Bytes(cc25519pub.rawRepresentation))
let hpkePriv = try PrivateKey(kem: .X25519, bytes: Bytes(ckPriv.rawRepresentation))
let hpkePub = try PublicKey(kem: .X25519, bytes: Bytes(ckPub.rawRepresentation))

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

let cc256priv = try CryptoKit.P256.KeyAgreement.PrivateKey(derRepresentation: hpke256priv.der)
let cc256pub = try CryptoKit.P256.KeyAgreement.PublicKey(derRepresentation: hpke256pub.der)
let ckPriv = try CryptoKit.P256.KeyAgreement.PrivateKey(derRepresentation: hpkePriv.der)
let ckPub = try CryptoKit.P256.KeyAgreement.PublicKey(derRepresentation: hpkePub.der)

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

let cc25519priv = try CryptoKit.Curve25519.KeyAgreement.PrivateKey(rawRepresentation: hpke25519priv.bytes)
let cc25519pub = try CryptoKit.Curve25519.KeyAgreement.PublicKey(rawRepresentation: hpke25519pub.bytes)
let ckPriv = try CryptoKit.Curve25519.KeyAgreement.PrivateKey(rawRepresentation: hpkePriv.bytes)
let ckPub = try CryptoKit.Curve25519.KeyAgreement.PublicKey(rawRepresentation: hpkePub.bytes)

<h2 id="dep"><b>Dependencies</b></h2>
<h2 id="basic9"><b>Performance</b></h2>
SwiftHPKE's encryption and decryption performance was measured on an iMac 2021, Apple M1 chip.
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.
<table width="90%">
<tr><th align="left" width="16%">KEM</th><th align="right" width="28%">Sender</th><th align="right" width="28%">Recipient</th></tr>
<tr><td>P256</td><td align="right">7 mSec</td><td align="right">6 mSec</td></tr>
<tr><td>P384</td><td align="right">20 mSec</td><td align="right">17 mSec</td></tr>
<tr><td>P521</td><td align="right">46 mSec</td><td align="right">39 mSec</td></tr>
<tr><td>X25519</td><td align="right">0.14 mSec</td><td align="right">0.09 mSec</td></tr>
<tr><td>X448</td><td align="right">1.1 mSec</td><td align="right">0.5 mSec</td></tr>
</table>
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.
<table width="90%">
<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>
<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>
<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>
<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>
</table>

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

dependencies: [
.package(url: "https://github.com/leif-ibsen/ASN1", from: "2.1.0"),
.package(url: "https://github.com/leif-ibsen/BigInt", from: "1.13.0"),
.package(url: "https://github.com/leif-ibsen/BigInt", from: "1.14.0"),
],

<h2 id="ref"><b>References</b></h2>
Expand Down
3 changes: 0 additions & 3 deletions Sources/SwiftHPKE/CipherSuite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ public typealias Byte = UInt8
/// Array of unsigned 8 bit values
public typealias Bytes = [UInt8]


/// A HPKE CipherSuite
///
/// A CipherSuite instance combines a *Key Encapsulation Mechanism* (KEM), a *Key Derivation Function* (KDF)
/// and a *AEAD Encryption Algorithm* (AEAD).
/// It can encrypt or decrypt a single message in one of four modes:
Expand Down
2 changes: 0 additions & 2 deletions Sources/SwiftHPKE/PrivateKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import ASN1
import BigInt

/// A HPKE private key
///
/// There are five different private key types corresponding to the five KEM's
///
/// * P256 - the key is a 32 byte value corresponding to a NIST curve secp256r1 private key
Expand Down
6 changes: 3 additions & 3 deletions Sources/SwiftHPKE/PublicKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import ASN1
import BigInt

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

// MARK: Initializers

/// Creates a PublicKey from its type and key bytes
/// Creates a PublicKey from its type and key bytes.<br/>
/// For types P256, P384 and P521 the key bytes represents
/// either a compressed curve point or an uncompressed curve point.
///
/// - Parameters:
/// - kem: The key type
Expand Down
5 changes: 2 additions & 3 deletions Sources/SwiftHPKE/Recipient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
// Created by Leif Ibsen on 19/06/2023.
//

/// Recipient
///
/// Based on its *CipherSuite*, a *Recipient* instance can decrypt a sequence of messages in one of four modes:
///
/// * Base mode
/// * Preshared key mode
/// * Authenticated mode
/// * Authenticated, preshared key mode
///
/// The decryption of the messages must be done in the order in which they were encrypted
/// The decryption of the messages must be done in the order in which they were encrypted.<br/>
/// A *Recipient* instance can also retrieve a generated export secret.
public class Recipient {

let suite: CipherSuite
Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftHPKE/Sender.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
// Created by Leif Ibsen on 19/06/2023.
//

/// Sender
///
/// Based on its *CipherSuite*, a *Sender* instance can encrypt a sequence of messages in one of four modes:
/// * Base mode
/// * Preshared key mode
/// * Authenticated mode
/// * Authenticated, preshared key mode
///
/// A *Sender* instance can also generate an export secret that only the recipient can know.
public class Sender {

let suite: CipherSuite
Expand Down
24 changes: 12 additions & 12 deletions Sources/SwiftHPKE/X255_448/Field25519.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,23 +140,23 @@ struct Field25519: CustomStringConvertible {
}

func add(_ x: Field25519) -> Field25519 {
var v = Field25519(
self.l0 + x.l0,
self.l1 + x.l1,
self.l2 + x.l2,
self.l3 + x.l3,
self.l4 + x.l4)
var v = self
v.l0 += x.l0
v.l1 += x.l1
v.l2 += x.l2
v.l3 += x.l3
v.l4 += x.l4
v.carryPropagate()
return v
}

func sub(_ x: Field25519) -> Field25519 {
var v = Field25519(
(self.l0 + 0xfffffffffffda) - x.l0,
(self.l1 + 0xffffffffffffe) - x.l1,
(self.l2 + 0xffffffffffffe) - x.l2,
(self.l3 + 0xffffffffffffe) - x.l3,
(self.l4 + 0xffffffffffffe) - x.l4)
var v = self
v.l0 += 0xfffffffffffda - x.l0
v.l1 += 0xffffffffffffe - x.l1
v.l2 += 0xffffffffffffe - x.l2
v.l3 += 0xffffffffffffe - x.l3
v.l4 += 0xffffffffffffe - x.l4
v.carryPropagate()
return v
}
Expand Down
11 changes: 8 additions & 3 deletions Sources/SwiftHPKE/X255_448/Field448.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,14 @@ struct Field448: CustomStringConvertible {

func add(_ a: Field448) -> Field448 {
var x = self
for i in 0 ..< 8 {
x.l[i] &+= a.l[i]
}
x.l[0] &+= a.l[0]
x.l[1] &+= a.l[1]
x.l[2] &+= a.l[2]
x.l[3] &+= a.l[3]
x.l[4] &+= a.l[4]
x.l[5] &+= a.l[5]
x.l[6] &+= a.l[6]
x.l[7] &+= a.l[7]
x.reduce()
return x
}
Expand Down
85 changes: 85 additions & 0 deletions Tests/SwiftHPKETests/WycheproofP256Test.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// WycheproofP256Test.swift
//
//
// Created by Leif Ibsen on 06/09/2023.
//

import XCTest
@testable import SwiftHPKE

// Test vectors from project Wycheproof - ecdh_secp256r1_test.json
final class WycheproofP256Test: XCTestCase {

static func hex2bytes(_ x: String) -> Bytes {
let b = [Byte](x.utf8)
var bytes = Bytes(repeating: 0, count: b.count / 2)
for i in 0 ..< bytes.count {
let b0 = b[2 * i]
let b1 = b[2 * i + 1]
bytes[i] = ((b0 > 57 ? b0 - 97 + 10 : b0 - 48) << 4) | (b1 > 57 ? b1 - 97 + 10 : b1 - 48)
}
return bytes
}

struct dhTest {

let pubKey: Bytes
let privKey: Bytes
let shared: Bytes

init(_ pubKey: String, _ privKey: String, _ shared: String) {
self.pubKey = hex2bytes(pubKey)
self.privKey = hex2bytes(privKey)
self.shared = hex2bytes(shared)
}
}

let tests256: [dhTest] = [
// tcId = 1, normal case
dhTest(
"0462d5bd3372af75fe85a040715d0f502428e07046868b0bfdfa61d731afe44f26ac333a93a9e70a81cd5a95b5bf8d13990eb741c8c38872b4a07d275a014e30cf",
"0612465c89a023ab17855b0a6bcebfd3febb53aef84138647b5352e02c10c346",
"53020d908b0219328b658b525f26780e3ae12bcd952bb25a93bc0895e1714285"),
// tcId = 2, compressed public key
dhTest(
"0362d5bd3372af75fe85a040715d0f502428e07046868b0bfdfa61d731afe44f26",
"0612465c89a023ab17855b0a6bcebfd3febb53aef84138647b5352e02c10c346",
"53020d908b0219328b658b525f26780e3ae12bcd952bb25a93bc0895e1714285"),
// tcId = 3, shared secret has x-coordinate that satisfies x**2 = 0
dhTest(
"0458fd4168a87795603e2b04390285bdca6e57de6027fe211dd9d25e2212d29e62080d36bd224d7405509295eed02a17150e03b314f96da37445b0d1d29377d12c",
"0a0d622a47e48f6bc1038ace438c6f528aa00ad2bd1da5f13ee46bf5f633d71a",
"0000000000000000000000000000000000000000000000000000000000000000"),
// tcId = 4, shared secret has x-coordinate p-3
dhTest(
"04a1ecc24bf0d0053d23f5fd80ddf1735a1925039dc1176c581a7e795163c8b9ba2cb5a4e4d5109f4527575e3137b83d79a9bcb3faeff90d2aca2bed71bb523e7e",
"0a0d622a47e48f6bc1038ace438c6f528aa00ad2bd1da5f13ee46bf5f633d71a",
"ffffffff00000001000000000000000000000000fffffffffffffffffffffffc"),
// tcId = 45, y-coordinate of the public key is small
dhTest(
"043cbc1b31b43f17dc200dd70c2944c04c6cb1b082820c234a300b05b7763844c74fde0a4ef93887469793270eb2ff148287da9265b0334f9e2609aac16e8ad503",
"0a0d622a47e48f6bc1038ace438c6f528aa00ad2bd1da5f13ee46bf5f633d71a",
"7fffffffffffffffffffffffeecf2230ffffffffffffffffffffffffffffffff"),
// tcId = 51, y-coordinate of the public key is large
dhTest(
"043cbc1b31b43f17dc200dd70c2944c04c6cb1b082820c234a300b05b7763844c7b021f5b006c778ba686cd8f14d00eb7d78256d9b4fccb061d9f6553e91752afc",
"0a0d622a47e48f6bc1038ace438c6f528aa00ad2bd1da5f13ee46bf5f633d71a",
"7fffffffffffffffffffffffeecf2230ffffffffffffffffffffffffffffffff"),
// tcId = 228, point with coordinate y = 1
dhTest(
"0409e78d4ef60d05f750f6636209092bc43cbdd6b47e11a9de20a9feb2a50bb96c0000000000000000000000000000000000000000000000000000000000000001",
"00809c461d8b39163537ff8f5ef5b977e4cdb980e70e38a7ee0b37cc876729e9ff",
"28f67757acc28b1684ba76ffd534aed42d45b8b3f10b82a5699416eff7199a74"),
]

func test256() throws {
let kem = KEMStructure(.P256)
for test in tests256 {
let priv = try PrivateKey(kem: .P256, bytes: test.privKey)
let pub = try PublicKey(kem: .P256, bytes: test.pubKey)
XCTAssertEqual(try kem.DH(priv, pub), test.shared)
}
}

}
80 changes: 80 additions & 0 deletions Tests/SwiftHPKETests/WycheproofP384Test.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// WycheproofP384Test.swift
//
//
// Created by Leif Ibsen on 06/09/2023.
//

import XCTest
@testable import SwiftHPKE

// Test vectors from project Wycheproof - ecdh_secp384r1_test.json
final class WycheproofP384Test: XCTestCase {

static func hex2bytes(_ x: String) -> Bytes {
let b = [Byte](x.utf8)
var bytes = Bytes(repeating: 0, count: b.count / 2)
for i in 0 ..< bytes.count {
let b0 = b[2 * i]
let b1 = b[2 * i + 1]
bytes[i] = ((b0 > 57 ? b0 - 97 + 10 : b0 - 48) << 4) | (b1 > 57 ? b1 - 97 + 10 : b1 - 48)
}
return bytes
}

struct dhTest {

let pubKey: Bytes
let privKey: Bytes
let shared: Bytes

init(_ pubKey: String, _ privKey: String, _ shared: String) {
self.pubKey = hex2bytes(pubKey)
self.privKey = hex2bytes(privKey)
self.shared = hex2bytes(shared)
}
}

let tests384: [dhTest] = [
// tcId = 1, normal case
dhTest(
"04790a6e059ef9a5940163183d4a7809135d29791643fc43a2f17ee8bf677ab84f791b64a6be15969ffa012dd9185d8796d9b954baa8a75e82df711b3b56eadff6b0f668c3b26b4b1aeb308a1fcc1c680d329a6705025f1c98a0b5e5bfcb163caa",
"766e61425b2da9f846c09fc3564b93a6f8603b7392c785165bf20da948c49fd1fb1dee4edd64356b9f21c588b75dfd81",
"6461defb95d996b24296f5a1832b34db05ed031114fbe7d98d098f93859866e4de1e229da71fef0c77fe49b249190135"),
// tcId = 2, compressed public key
dhTest(
"02790a6e059ef9a5940163183d4a7809135d29791643fc43a2f17ee8bf677ab84f791b64a6be15969ffa012dd9185d8796",
"766e61425b2da9f846c09fc3564b93a6f8603b7392c785165bf20da948c49fd1fb1dee4edd64356b9f21c588b75dfd81",
"6461defb95d996b24296f5a1832b34db05ed031114fbe7d98d098f93859866e4de1e229da71fef0c77fe49b249190135"),
// tcId = 3, shared secret has x-coordinate that satisfies x**2 = 0
dhTest(
"04490e96d17f4c6ceccd45def408cea33e9704a5f1b01a3de2eaaa3409fd160d78d395d6b3b003d71fd1f590fad95bf1c9d8665efc2070d059aa847125c2f707435955535c7c5df6d6c079ec806dce6b6849d337140db7ca50616f9456de1323c4",
"00a2b6442a37f8a3759d2cb91df5eca75b14f5a6766da8035cc1943b15a8e4ebb6025f373be334080f22ab821a3535a6a7",
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
// tcId = 49, y-coordinate of the public key is small
dhTest(
"04bfeb47fb40a65878e6b642f40b8e15022ade9ecfa8cb618043063494e2bc5d2df10d36f37869b58ef12dcc35e3982835fd2e55ec41fdfe8cabbbb7bcd8163645a19e9dac59630f3fe93b208094ff87cd461b53cef53482e70e2e8ea87200cc3f",
"00a2b6442a37f8a3759d2cb91df5eca75b14f5a6766da8035cc1943b15a8e4ebb6025f373be334080f22ab821a3535a6a7",
"0000000000000000000000000000000000000000000000000000000036a2907c00000000000000000000000000000000"),
// tcId = 51, y-coordinate of the public key is large
dhTest(
"04bfeb47fb40a65878e6b642f40b8e15022ade9ecfa8cb618043063494e2bc5d2df10d36f37869b58ef12dcc35e398283502d1aa13be0201735444484327e9c9ba5e616253a69cf0c016c4df7f6b007831b9e4ac300acb7d18f1d171588dff33c0",
"00a2b6442a37f8a3759d2cb91df5eca75b14f5a6766da8035cc1943b15a8e4ebb6025f373be334080f22ab821a3535a6a7",
"0000000000000000000000000000000000000000000000000000000036a2907c00000000000000000000000000000000"),
// tcId = 585, point with coordinate y = 1
dhTest(
"042261b2bf605c22f2f3aef6338719b2c486388ad5240719a5257315969ef01ba27f0a104c89704773a81fdabee6ab5c78000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
"00c1781d86cac2c052b7e4f48cef415c5c133052f4e504397e75e4d7cd0ca149da0b4988b8a6ded5ceae4b580691376187",
"c923fb0d4b24e996e5e0d5df151d3c26b1f61c05b17b7fb39fc8590b47eeaff34709f6f7328923bdcaf7e8e413d77ddc"),
]

func test384() throws {
let kem = KEMStructure(.P384)
for test in tests384 {
let priv = try PrivateKey(kem: .P384, bytes: test.privKey)
let pub = try PublicKey(kem: .P384, bytes: test.pubKey)
XCTAssertEqual(try kem.DH(priv, pub), test.shared)
}
}

}
Loading

0 comments on commit 1012c87

Please sign in to comment.