Skip to content

Commit

Permalink
update readme (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
tayloraswift authored Nov 13, 2022
1 parent d213ae0 commit 9ea135f
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 91 deletions.
18 changes: 11 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ let package:Package = .init(
name: "swift-hash",
products:
[
.library(name: "Base16", targets: ["Base16"]),
.library(name: "Base64", targets: ["Base64"]),
.library(name: "SHA2", targets: ["SHA2"]),
.library(name: "CRC", targets: ["CRC"]),
.library(name: "Base16", targets: ["Base16"]),
.library(name: "Base64", targets: ["Base64"]),
.library(name: "CRC", targets: ["CRC"]),
.library(name: "MessageAuthentication", targets: ["MessageAuthentication"]),
.library(name: "SHA2", targets: ["SHA2"]),

.library(name: "Testing", targets: ["Testing"]),
.library(name: "Testing", targets: ["Testing"]),
],
dependencies:
[
Expand All @@ -62,16 +63,19 @@ let package:Package = .init(
.target(name: "BaseDigits"),
]),

.target(name: "SHA2",
.target(name: "CRC",
dependencies:
[
.target(name: "Base16"),
]),

.target(name: "CRC",
.target(name: "MessageAuthentication"),

.target(name: "SHA2",
dependencies:
[
.target(name: "Base16"),
.target(name: "MessageAuthentication"),
]),

.target(name: "Testing"),
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div align="center">

***`hash`***<br>`0.3.0`
***`hash`***<br>`0.4.0`

[![ci status](https://github.com/kelvin13/swift-hash/actions/workflows/build.yml/badge.svg)](https://github.com/kelvin13/swift-hash/actions/workflows/build.yml)
[![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)
Expand Down Expand Up @@ -30,6 +30,10 @@ The package vends the following library products:

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

1. [`MessageAuthentication`](Sources/MessageAuthentication)

Implements [hash-based message authentication codes](https://en.wikipedia.org/wiki/HMAC) (HMACs) through protocols that types in the other modules conform to.

1. [`SHA2`](Sources/SHA2)

Implements the [SHA-256](https://en.wikipedia.org/wiki/SHA-2) and HMAC-SHA-256 hashing functions.
Implements the [SHA-256](https://en.wikipedia.org/wiki/SHA-2) hashing function.
64 changes: 64 additions & 0 deletions Sources/MessageAuthentication/MessageAuthenticationHash.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/// A hash that can be used to generate a message authentication code (MAC).
public
protocol MessageAuthenticationHash:RandomAccessCollection where Index == Int, Element == UInt8
{
/// The natural block stride of this hash function.
static
var stride:Int { get }

/// The output size, in bytes, of this hash function.
static
var count:Int { get }

/// Computes an instance of this hash for the given message.
init<Message>(hashing message:Message)
where Message:Collection, Message.Element == UInt8
}
extension MessageAuthenticationHash
{
@inlinable public
var endIndex:Int
{
self.startIndex + Self.count
}
@inlinable public
var count:Int
{
Self.count
}
}
extension MessageAuthenticationHash
{
/// Computes a hash-based message authentication code (HMAC) for
/// the given message using the given key.
///
/// This initializer computes an instance of ``MessageAuthenticationKey``
/// and uses it to generate an instance of ``Self``. If you are reusing
/// the same `key` multiple times, it is more efficient to compute the
/// message authentication key externally and call its
/// ``MessageAuthenticationKey.authenticate(_:)`` method instead.
@inlinable public
init<Message, Key>(authenticating message:Message, key:Key)
where Message:Sequence, Message.Element == UInt8,
Key:Collection, Key.Element == UInt8
{
let key:MessageAuthenticationKey<Self> = .init(key)
self = key.authenticate(message)
}
}

extension MessageAuthenticationHash
{
/// Derives an encryption key from a password and salt.
/// This is a password-based key derivation function
/// ([PBKDR2](https://en.wikipedia.org/wiki/PBKDF2)).
@inlinable public static
func pbkdf2<Password, Salt>(password:Password, salt:Salt, iterations:Int,
blocks:Int = 1) -> [UInt8]
where Password:Collection, Password.Element == UInt8,
Salt:Collection, Salt.Element == UInt8
{
let key:MessageAuthenticationKey<Self> = .init(password)
return key.derive(salt: salt, iterations: iterations, blocks: blocks)
}
}
80 changes: 80 additions & 0 deletions Sources/MessageAuthentication/MessageAuthenticationKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/// A precomputed message authentication key, which can be used to compute
/// hash-based message authentication codes ([HMACs](https://en.wikipedia.org/wiki/HMAC)).
///
/// Using this type to generate authentication codes for many messages with
/// the same base key is faster than repeatedly calling
/// ``MessageAuthenticationHash.init(authenticating:key:)``.
@frozen public
struct MessageAuthenticationKey<Hash> where Hash:MessageAuthenticationHash
{
public
let inner:[UInt8]
public
let outer:[UInt8]

/// Creates a message authentication key from the given base key.
@inlinable public
init<Key>(_ key:Key) where Key:Collection, Key.Element == UInt8
{
let normalized:[UInt8]
let count:Int = key.count
if count > Hash.stride
{
let key:Hash = .init(hashing: key)
normalized = [UInt8].init(key) + repeatElement(0, count: Hash.stride - Hash.count)
}
else if count < Hash.stride
{
normalized = [UInt8].init(key) + repeatElement(0, count: Hash.stride - count)
}
else
{
normalized = [UInt8].init(key)
}

self.inner = normalized.map { $0 ^ 0x36 }
self.outer = normalized.map { $0 ^ 0x5c }
}
}
extension MessageAuthenticationKey
{
/// Computes a hash-based message authentication code
/// ([HMAC](https://en.wikipedia.org/wiki/HMAC)) for the given message
/// using this key.
@inlinable public
func authenticate<Message>(_ message:Message) -> Hash
where Message:Sequence, Message.Element == UInt8
{
.init(hashing: outer + Hash.init(hashing: inner + message))
}
/// Derives an encryption key using this message authentication key and
/// the given salt. If this message authentication key was computed from
/// a password, then this functions as a password-based key derivation
/// function ([PBKDR2](https://en.wikipedia.org/wiki/PBKDF2)).
@inlinable public
func derive<Salt>(salt:Salt, iterations:Int, blocks:Int = 1) -> [UInt8]
where Salt:Collection, Salt.Element == UInt8
{
var output:[UInt8] = []
output.reserveCapacity(blocks * Hash.count)
for block:UInt32 in 1 ... UInt32.init(blocks)
{
let salt:[UInt8] = withUnsafeBytes(of: block.bigEndian) { [UInt8].init(salt) + $0 }

var hash:Hash = self.authenticate(salt)
var block:[UInt8] = .init(hash)

for _ in 1 ..< iterations
{
hash = self.authenticate(hash)
for (index, byte):(Int, UInt8) in zip(block.indices, hash)
{
block[index] ^= byte
}
}

output.append(contentsOf: block)
}
return output
}
}
120 changes: 50 additions & 70 deletions Sources/SHA2/SHA256.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import Base16
import MessageAuthentication

#if swift(>=5.5)
extension SHA256:Sendable {}
#endif

@frozen public
struct SHA256:RandomAccessCollection, Hashable
struct SHA256
{
public
typealias Words = (UInt32, UInt32, UInt32, UInt32, UInt32, UInt32, UInt32, UInt32)
Expand Down Expand Up @@ -34,54 +35,6 @@ struct SHA256:RandomAccessCollection, Hashable
public
var words:Words

@inlinable public
var startIndex:Int
{
0
}
@inlinable public
var endIndex:Int
{
32
}
@inlinable public
subscript(index:Int) -> UInt8
{
withUnsafePointer(to: self.words)
{
$0.withMemoryRebound(to: UInt32.self, capacity: 8)
{
// big-endian
UInt8.init(($0[index >> 2] << ((index & 3) << 3)) >> 24)
}
}
}

@inlinable public static
func == (lhs:Self, rhs:Self) -> Bool
{
lhs.words.0 == rhs.words.0 &&
lhs.words.1 == rhs.words.1 &&
lhs.words.2 == rhs.words.2 &&
lhs.words.3 == rhs.words.3 &&
lhs.words.4 == rhs.words.4 &&
lhs.words.5 == rhs.words.5 &&
lhs.words.6 == rhs.words.6 &&
lhs.words.7 == rhs.words.7
}
@inlinable public
func hash(into hasher:inout Hasher)
{
self.words.0.hash(into: &hasher)
self.words.1.hash(into: &hasher)
self.words.2.hash(into: &hasher)
self.words.3.hash(into: &hasher)
self.words.4.hash(into: &hasher)
self.words.5.hash(into: &hasher)
self.words.6.hash(into: &hasher)
self.words.7.hash(into: &hasher)
}

@inlinable public
init(words:Words =
(
Expand Down Expand Up @@ -225,32 +178,59 @@ struct SHA256:RandomAccessCollection, Hashable
}
}

extension SHA256
extension SHA256:Equatable
{
@inlinable public static
func hmac<Message, Key>(_ message:Message, key:Key) -> Self
where Message:Sequence, Message.Element == UInt8,
Key:Collection, Key.Element == UInt8
func == (lhs:Self, rhs:Self) -> Bool
{
let normalized:[UInt8]
if key.count > 64
{
normalized = [UInt8].init(Self.init(hashing: key)) +
repeatElement(0, count: 32)
}
else if key.count < 64
{
normalized = [UInt8].init(key) +
repeatElement(0, count: 64 - key.count)
}
else
lhs.words.0 == rhs.words.0 &&
lhs.words.1 == rhs.words.1 &&
lhs.words.2 == rhs.words.2 &&
lhs.words.3 == rhs.words.3 &&
lhs.words.4 == rhs.words.4 &&
lhs.words.5 == rhs.words.5 &&
lhs.words.6 == rhs.words.6 &&
lhs.words.7 == rhs.words.7
}
}
extension SHA256:Hashable
{
@inlinable public
func hash(into hasher:inout Hasher)
{
self.words.0.hash(into: &hasher)
self.words.1.hash(into: &hasher)
self.words.2.hash(into: &hasher)
self.words.3.hash(into: &hasher)
self.words.4.hash(into: &hasher)
self.words.5.hash(into: &hasher)
self.words.6.hash(into: &hasher)
self.words.7.hash(into: &hasher)
}
}
extension SHA256:MessageAuthenticationHash
{
public static
let stride:Int = 64
public static
let count:Int = 32

@inlinable public
var startIndex:Int
{
0
}
@inlinable public
subscript(index:Int) -> UInt8
{
withUnsafePointer(to: self.words)
{
normalized = [UInt8].init(key)
$0.withMemoryRebound(to: UInt32.self, capacity: 8)
{
// big-endian
UInt8.init(($0[index >> 2] << ((index & 3) << 3)) >> 24)
}
}

let inner:[UInt8] = normalized.map { $0 ^ 0x36 },
outer:[UInt8] = normalized.map { $0 ^ 0x5c }
return .init(hashing: outer + Self.init(hashing: inner + message))
}
}

Expand Down
Loading

0 comments on commit 9ea135f

Please sign in to comment.