Skip to content

Commit 9ea135f

Browse files
authored
update readme (#5)
1 parent d213ae0 commit 9ea135f

File tree

7 files changed

+302
-91
lines changed

7 files changed

+302
-91
lines changed

Package.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ let package:Package = .init(
3636
name: "swift-hash",
3737
products:
3838
[
39-
.library(name: "Base16", targets: ["Base16"]),
40-
.library(name: "Base64", targets: ["Base64"]),
41-
.library(name: "SHA2", targets: ["SHA2"]),
42-
.library(name: "CRC", targets: ["CRC"]),
39+
.library(name: "Base16", targets: ["Base16"]),
40+
.library(name: "Base64", targets: ["Base64"]),
41+
.library(name: "CRC", targets: ["CRC"]),
42+
.library(name: "MessageAuthentication", targets: ["MessageAuthentication"]),
43+
.library(name: "SHA2", targets: ["SHA2"]),
4344

44-
.library(name: "Testing", targets: ["Testing"]),
45+
.library(name: "Testing", targets: ["Testing"]),
4546
],
4647
dependencies:
4748
[
@@ -62,16 +63,19 @@ let package:Package = .init(
6263
.target(name: "BaseDigits"),
6364
]),
6465

65-
.target(name: "SHA2",
66+
.target(name: "CRC",
6667
dependencies:
6768
[
6869
.target(name: "Base16"),
6970
]),
7071

71-
.target(name: "CRC",
72+
.target(name: "MessageAuthentication"),
73+
74+
.target(name: "SHA2",
7275
dependencies:
7376
[
7477
.target(name: "Base16"),
78+
.target(name: "MessageAuthentication"),
7579
]),
7680

7781
.target(name: "Testing"),

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div align="center">
22

3-
***`hash`***<br>`0.3.0`
3+
***`hash`***<br>`0.4.0`
44

55
[![ci status](https://github.com/kelvin13/swift-hash/actions/workflows/build.yml/badge.svg)](https://github.com/kelvin13/swift-hash/actions/workflows/build.yml)
66
[![ci status](https://github.com/kelvin13/swift-hash/actions/workflows/build-devices.yml/badge.svg)](https://github.com/kelvin13/swift-hash/actions/workflows/build-devices.yml)
@@ -30,6 +30,10 @@ The package vends the following library products:
3030

3131
Implements [CRC-32](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) checksums.
3232

33+
1. [`MessageAuthentication`](Sources/MessageAuthentication)
34+
35+
Implements [hash-based message authentication codes](https://en.wikipedia.org/wiki/HMAC) (HMACs) through protocols that types in the other modules conform to.
36+
3337
1. [`SHA2`](Sources/SHA2)
3438

35-
Implements the [SHA-256](https://en.wikipedia.org/wiki/SHA-2) and HMAC-SHA-256 hashing functions.
39+
Implements the [SHA-256](https://en.wikipedia.org/wiki/SHA-2) hashing function.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/// A hash that can be used to generate a message authentication code (MAC).
2+
public
3+
protocol MessageAuthenticationHash:RandomAccessCollection where Index == Int, Element == UInt8
4+
{
5+
/// The natural block stride of this hash function.
6+
static
7+
var stride:Int { get }
8+
9+
/// The output size, in bytes, of this hash function.
10+
static
11+
var count:Int { get }
12+
13+
/// Computes an instance of this hash for the given message.
14+
init<Message>(hashing message:Message)
15+
where Message:Collection, Message.Element == UInt8
16+
}
17+
extension MessageAuthenticationHash
18+
{
19+
@inlinable public
20+
var endIndex:Int
21+
{
22+
self.startIndex + Self.count
23+
}
24+
@inlinable public
25+
var count:Int
26+
{
27+
Self.count
28+
}
29+
}
30+
extension MessageAuthenticationHash
31+
{
32+
/// Computes a hash-based message authentication code (HMAC) for
33+
/// the given message using the given key.
34+
///
35+
/// This initializer computes an instance of ``MessageAuthenticationKey``
36+
/// and uses it to generate an instance of ``Self``. If you are reusing
37+
/// the same `key` multiple times, it is more efficient to compute the
38+
/// message authentication key externally and call its
39+
/// ``MessageAuthenticationKey.authenticate(_:)`` method instead.
40+
@inlinable public
41+
init<Message, Key>(authenticating message:Message, key:Key)
42+
where Message:Sequence, Message.Element == UInt8,
43+
Key:Collection, Key.Element == UInt8
44+
{
45+
let key:MessageAuthenticationKey<Self> = .init(key)
46+
self = key.authenticate(message)
47+
}
48+
}
49+
50+
extension MessageAuthenticationHash
51+
{
52+
/// Derives an encryption key from a password and salt.
53+
/// This is a password-based key derivation function
54+
/// ([PBKDR2](https://en.wikipedia.org/wiki/PBKDF2)).
55+
@inlinable public static
56+
func pbkdf2<Password, Salt>(password:Password, salt:Salt, iterations:Int,
57+
blocks:Int = 1) -> [UInt8]
58+
where Password:Collection, Password.Element == UInt8,
59+
Salt:Collection, Salt.Element == UInt8
60+
{
61+
let key:MessageAuthenticationKey<Self> = .init(password)
62+
return key.derive(salt: salt, iterations: iterations, blocks: blocks)
63+
}
64+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/// A precomputed message authentication key, which can be used to compute
2+
/// hash-based message authentication codes ([HMACs](https://en.wikipedia.org/wiki/HMAC)).
3+
///
4+
/// Using this type to generate authentication codes for many messages with
5+
/// the same base key is faster than repeatedly calling
6+
/// ``MessageAuthenticationHash.init(authenticating:key:)``.
7+
@frozen public
8+
struct MessageAuthenticationKey<Hash> where Hash:MessageAuthenticationHash
9+
{
10+
public
11+
let inner:[UInt8]
12+
public
13+
let outer:[UInt8]
14+
15+
/// Creates a message authentication key from the given base key.
16+
@inlinable public
17+
init<Key>(_ key:Key) where Key:Collection, Key.Element == UInt8
18+
{
19+
let normalized:[UInt8]
20+
let count:Int = key.count
21+
if count > Hash.stride
22+
{
23+
let key:Hash = .init(hashing: key)
24+
normalized = [UInt8].init(key) + repeatElement(0, count: Hash.stride - Hash.count)
25+
}
26+
else if count < Hash.stride
27+
{
28+
normalized = [UInt8].init(key) + repeatElement(0, count: Hash.stride - count)
29+
}
30+
else
31+
{
32+
normalized = [UInt8].init(key)
33+
}
34+
35+
self.inner = normalized.map { $0 ^ 0x36 }
36+
self.outer = normalized.map { $0 ^ 0x5c }
37+
}
38+
}
39+
extension MessageAuthenticationKey
40+
{
41+
/// Computes a hash-based message authentication code
42+
/// ([HMAC](https://en.wikipedia.org/wiki/HMAC)) for the given message
43+
/// using this key.
44+
@inlinable public
45+
func authenticate<Message>(_ message:Message) -> Hash
46+
where Message:Sequence, Message.Element == UInt8
47+
{
48+
.init(hashing: outer + Hash.init(hashing: inner + message))
49+
}
50+
/// Derives an encryption key using this message authentication key and
51+
/// the given salt. If this message authentication key was computed from
52+
/// a password, then this functions as a password-based key derivation
53+
/// function ([PBKDR2](https://en.wikipedia.org/wiki/PBKDF2)).
54+
@inlinable public
55+
func derive<Salt>(salt:Salt, iterations:Int, blocks:Int = 1) -> [UInt8]
56+
where Salt:Collection, Salt.Element == UInt8
57+
{
58+
var output:[UInt8] = []
59+
output.reserveCapacity(blocks * Hash.count)
60+
for block:UInt32 in 1 ... UInt32.init(blocks)
61+
{
62+
let salt:[UInt8] = withUnsafeBytes(of: block.bigEndian) { [UInt8].init(salt) + $0 }
63+
64+
var hash:Hash = self.authenticate(salt)
65+
var block:[UInt8] = .init(hash)
66+
67+
for _ in 1 ..< iterations
68+
{
69+
hash = self.authenticate(hash)
70+
for (index, byte):(Int, UInt8) in zip(block.indices, hash)
71+
{
72+
block[index] ^= byte
73+
}
74+
}
75+
76+
output.append(contentsOf: block)
77+
}
78+
return output
79+
}
80+
}

Sources/SHA2/SHA256.swift

Lines changed: 50 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import Base16
2+
import MessageAuthentication
23

34
#if swift(>=5.5)
45
extension SHA256:Sendable {}
56
#endif
67

78
@frozen public
8-
struct SHA256:RandomAccessCollection, Hashable
9+
struct SHA256
910
{
1011
public
1112
typealias Words = (UInt32, UInt32, UInt32, UInt32, UInt32, UInt32, UInt32, UInt32)
@@ -34,54 +35,6 @@ struct SHA256:RandomAccessCollection, Hashable
3435
public
3536
var words:Words
3637

37-
@inlinable public
38-
var startIndex:Int
39-
{
40-
0
41-
}
42-
@inlinable public
43-
var endIndex:Int
44-
{
45-
32
46-
}
47-
@inlinable public
48-
subscript(index:Int) -> UInt8
49-
{
50-
withUnsafePointer(to: self.words)
51-
{
52-
$0.withMemoryRebound(to: UInt32.self, capacity: 8)
53-
{
54-
// big-endian
55-
UInt8.init(($0[index >> 2] << ((index & 3) << 3)) >> 24)
56-
}
57-
}
58-
}
59-
60-
@inlinable public static
61-
func == (lhs:Self, rhs:Self) -> Bool
62-
{
63-
lhs.words.0 == rhs.words.0 &&
64-
lhs.words.1 == rhs.words.1 &&
65-
lhs.words.2 == rhs.words.2 &&
66-
lhs.words.3 == rhs.words.3 &&
67-
lhs.words.4 == rhs.words.4 &&
68-
lhs.words.5 == rhs.words.5 &&
69-
lhs.words.6 == rhs.words.6 &&
70-
lhs.words.7 == rhs.words.7
71-
}
72-
@inlinable public
73-
func hash(into hasher:inout Hasher)
74-
{
75-
self.words.0.hash(into: &hasher)
76-
self.words.1.hash(into: &hasher)
77-
self.words.2.hash(into: &hasher)
78-
self.words.3.hash(into: &hasher)
79-
self.words.4.hash(into: &hasher)
80-
self.words.5.hash(into: &hasher)
81-
self.words.6.hash(into: &hasher)
82-
self.words.7.hash(into: &hasher)
83-
}
84-
8538
@inlinable public
8639
init(words:Words =
8740
(
@@ -225,32 +178,59 @@ struct SHA256:RandomAccessCollection, Hashable
225178
}
226179
}
227180

228-
extension SHA256
181+
extension SHA256:Equatable
229182
{
230183
@inlinable public static
231-
func hmac<Message, Key>(_ message:Message, key:Key) -> Self
232-
where Message:Sequence, Message.Element == UInt8,
233-
Key:Collection, Key.Element == UInt8
184+
func == (lhs:Self, rhs:Self) -> Bool
234185
{
235-
let normalized:[UInt8]
236-
if key.count > 64
237-
{
238-
normalized = [UInt8].init(Self.init(hashing: key)) +
239-
repeatElement(0, count: 32)
240-
}
241-
else if key.count < 64
242-
{
243-
normalized = [UInt8].init(key) +
244-
repeatElement(0, count: 64 - key.count)
245-
}
246-
else
186+
lhs.words.0 == rhs.words.0 &&
187+
lhs.words.1 == rhs.words.1 &&
188+
lhs.words.2 == rhs.words.2 &&
189+
lhs.words.3 == rhs.words.3 &&
190+
lhs.words.4 == rhs.words.4 &&
191+
lhs.words.5 == rhs.words.5 &&
192+
lhs.words.6 == rhs.words.6 &&
193+
lhs.words.7 == rhs.words.7
194+
}
195+
}
196+
extension SHA256:Hashable
197+
{
198+
@inlinable public
199+
func hash(into hasher:inout Hasher)
200+
{
201+
self.words.0.hash(into: &hasher)
202+
self.words.1.hash(into: &hasher)
203+
self.words.2.hash(into: &hasher)
204+
self.words.3.hash(into: &hasher)
205+
self.words.4.hash(into: &hasher)
206+
self.words.5.hash(into: &hasher)
207+
self.words.6.hash(into: &hasher)
208+
self.words.7.hash(into: &hasher)
209+
}
210+
}
211+
extension SHA256:MessageAuthenticationHash
212+
{
213+
public static
214+
let stride:Int = 64
215+
public static
216+
let count:Int = 32
217+
218+
@inlinable public
219+
var startIndex:Int
220+
{
221+
0
222+
}
223+
@inlinable public
224+
subscript(index:Int) -> UInt8
225+
{
226+
withUnsafePointer(to: self.words)
247227
{
248-
normalized = [UInt8].init(key)
228+
$0.withMemoryRebound(to: UInt32.self, capacity: 8)
229+
{
230+
// big-endian
231+
UInt8.init(($0[index >> 2] << ((index & 3) << 3)) >> 24)
232+
}
249233
}
250-
251-
let inner:[UInt8] = normalized.map { $0 ^ 0x36 },
252-
outer:[UInt8] = normalized.map { $0 ^ 0x5c }
253-
return .init(hashing: outer + Self.init(hashing: inner + message))
254234
}
255235
}
256236

0 commit comments

Comments
 (0)