Skip to content

Commit d9f3641

Browse files
[WWDC25] Add SHA-3 implementation backed by XKCP (#397)
## Motivation CryptoKit is adding API for SHA-3, which BoringSSL does not support. To maintain API parity, we need to provide a backing implementation. For this, we can use XKCP[^1], which provides the reference implementation, as well as several optimized solutions, suitable for vendoring into other projects. ## Modifications The following changes have been made in separate commits to help with the review: - Add vendor-xkcp.sh script - Revendor xkcp#master (heads/master-0-g11297f5) - Add CXCKP target with modulemap and umbrella header - Add CXKCPTests test target with simple test vectors - Add CXKCPShims with function wrappers for macros to call from Swift - Remove #if false guard from HashFunctions_SHA3.swift - Add SHA-3 implementation backed by libXKCP - Add new DigestImplSHA3 platform-specific type alias - Remove #if false from DigestsTests.swift to get SHA-3 tests ## Result Swift Crypto provides functioning SHA-3 API. ## Notes This PR is for the `wwdc-25` side branch. [^1]: https://github.com/XKCP/XKCP
1 parent 9b06971 commit d9f3641

39 files changed

+3509
-27
lines changed

.github/workflows/pull_request.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ jobs:
4444
{ "name": "CCryptoBoringSSL", "type": "source", "exceptions": [] },
4545
{ "name": "CCryptoBoringSSLShims", "type": "source", "exceptions": [] },
4646
{ "name": "CryptoBoringWrapper", "type": "source", "exceptions": [] },
47+
{ "name": "CXKCP", "type": "source", "exceptions": [] },
48+
{ "name": "CXKCPShims", "type": "source", "exceptions": [] },
4749
{ "name": "Crypto", "type": "source", "exceptions": [] },
4850
{ "name": "_CryptoExtras", "type": "source", "exceptions": [] },
4951
{ "name": "CCryptoBoringSSL", "type": "assembly", "exceptions": [ "*/AES/*.swift" ] }

.licenseignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ dev/update-benchmark-thresholds
4343
**/*.der
4444
.swiftformat
4545
Sources/CCryptoBoringSSL/*
46+
Sources/CXKCP/*
4647
**/*.swift.gyb
4748
scripts/*.patch
4849
scripts/gyb

.unacceptablelanguageignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
Sources/CCryptoBoringSSL/*
1+
Sources/CCryptoBoringSSL/*
2+
Sources/CXKCP/*

Package.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ if development || isFreeBSD {
4949
"CCryptoBoringSSL",
5050
"CCryptoBoringSSLShims",
5151
"CryptoBoringWrapper",
52+
"CXKCP",
53+
"CXKCPShims",
5254
]
5355
} else {
5456
let platforms: [Platform] = [
@@ -66,6 +68,8 @@ if development || isFreeBSD {
6668
.target(name: "CCryptoBoringSSL", condition: .when(platforms: platforms)),
6769
.target(name: "CCryptoBoringSSLShims", condition: .when(platforms: platforms)),
6870
.target(name: "CryptoBoringWrapper", condition: .when(platforms: platforms)),
71+
.target(name: "CXKCP", condition: .when(platforms: platforms)),
72+
.target(name: "CXKCPShims", condition: .when(platforms: platforms)),
6973
]
7074
}
7175

@@ -125,6 +129,21 @@ let package = Package(
125129
.define("OPENSSL_NO_ASM", .when(platforms: [Platform.wasi])),
126130
]
127131
),
132+
.target(
133+
name: "CXKCP",
134+
exclude: [
135+
"CMakeLists.txt"
136+
],
137+
cSettings: [
138+
.define("XKCP_has_KeccakP1600"),
139+
.headerSearchPath("include"),
140+
.headerSearchPath("high"),
141+
.headerSearchPath("low"),
142+
.headerSearchPath("low/KeccakP-1600"),
143+
.headerSearchPath("low/common"),
144+
.headerSearchPath("common"),
145+
]
146+
),
128147
.target(
129148
name: "CCryptoBoringSSLShims",
130149
dependencies: ["CCryptoBoringSSL"],
@@ -133,6 +152,14 @@ let package = Package(
133152
],
134153
resources: privacyManifestResource
135154
),
155+
.target(
156+
name: "CXKCPShims",
157+
dependencies: ["CXKCP"],
158+
exclude: privacyManifestExclude + [
159+
"CMakeLists.txt"
160+
],
161+
resources: privacyManifestResource
162+
),
136163
.target(
137164
name: "Crypto",
138165
dependencies: dependencies,
@@ -205,6 +232,7 @@ let package = Package(
205232
swiftSettings: swiftSettings
206233
),
207234
.testTarget(name: "CryptoBoringWrapperTests", dependencies: ["CryptoBoringWrapper"]),
235+
.testTarget(name: "CXKCPTests", dependencies: ["CXKCP"]),
208236
],
209237
cxxLanguageStandard: .cxx17
210238
)

Sources/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
add_subdirectory(CCryptoBoringSSL)
1616
add_subdirectory(CCryptoBoringSSLShims)
17+
add_subdirectory(CXKCP)
18+
add_subdirectory(CXKCPShims)
1719
add_subdirectory(CryptoBoringWrapper)
1820
add_subdirectory(Crypto)
1921
add_subdirectory(_CryptoExtras)

Sources/CXKCP/CMakeLists.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
##===----------------------------------------------------------------------===##
2+
##
3+
## This source file is part of the SwiftCrypto open source project
4+
##
5+
## Copyright (c) 2021 Apple Inc. and the SwiftCrypto project authors
6+
## Licensed under Apache License v2.0
7+
##
8+
## See LICENSE.txt for license information
9+
## See CONTRIBUTORS.txt for the list of SwiftCrypto project authors
10+
##
11+
## SPDX-License-Identifier: Apache-2.0
12+
##
13+
##===----------------------------------------------------------------------===##
14+
15+
add_library(CXKCP STATIC
16+
"FIPS202-opt64/KeccakHash.c"
17+
"FIPS202-opt64/KeccakP-1600-opt64.c"
18+
"FIPS202-opt64/KeccakSponge.c"
19+
"FIPS202-opt64/SimpleFIPS202.c"
20+
)
21+
22+
target_include_directories(CXKCP PUBLIC
23+
include)
24+
25+
target_compile_definitions(CXKCP PRIVATE
26+
$<$<PLATFORM_ID:Windows>:WIN32_LEAN_AND_MEAN>)
27+
target_link_libraries(CXKCP PUBLIC
28+
$<$<NOT:$<PLATFORM_ID:Darwin>>:dispatch>
29+
$<$<NOT:$<PLATFORM_ID:Darwin>>:Foundation>
30+
SwiftASN1)
31+
set_target_properties(CXKCP PROPERTIES
32+
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/include;${CMAKE_Swift_MODULE_DIRECTORY}")
33+
34+
set_property(GLOBAL APPEND PROPERTY SWIFT_CRYPTO_EXPORTS CXKCP)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
The eXtended Keccak Code Package (XKCP)
3+
https://github.com/XKCP/XKCP
4+
5+
Keccak, designed by Guido Bertoni, Joan Daemen, Michaël Peeters and Gilles Van Assche.
6+
7+
Implementation by the designers, hereby denoted as "the implementer".
8+
9+
For more information, feedback or questions, please refer to the Keccak Team website:
10+
https://keccak.team/
11+
12+
To the extent possible under law, the implementer has waived all copyright
13+
and related or neighboring rights to the source code in this file.
14+
http://creativecommons.org/publicdomain/zero/1.0/
15+
*/
16+
17+
#include <string.h>
18+
#include "KeccakHash.h"
19+
20+
/* ---------------------------------------------------------------- */
21+
22+
HashReturn Keccak_HashInitialize(Keccak_HashInstance *instance, unsigned int rate, unsigned int capacity, unsigned int hashbitlen, unsigned char delimitedSuffix)
23+
{
24+
HashReturn result;
25+
26+
if (delimitedSuffix == 0)
27+
return KECCAK_FAIL;
28+
result = (HashReturn)KeccakWidth1600_SpongeInitialize(&instance->sponge, rate, capacity);
29+
if (result != KECCAK_SUCCESS)
30+
return result;
31+
instance->fixedOutputLength = hashbitlen;
32+
instance->delimitedSuffix = delimitedSuffix;
33+
return KECCAK_SUCCESS;
34+
}
35+
36+
/* ---------------------------------------------------------------- */
37+
38+
HashReturn Keccak_HashUpdate(Keccak_HashInstance *instance, const BitSequence *data, BitLength databitlen)
39+
{
40+
if ((databitlen % 8) == 0)
41+
return (HashReturn)KeccakWidth1600_SpongeAbsorb(&instance->sponge, data, databitlen/8);
42+
else {
43+
HashReturn ret = (HashReturn)KeccakWidth1600_SpongeAbsorb(&instance->sponge, data, databitlen/8);
44+
if (ret == KECCAK_SUCCESS) {
45+
/* The last partial byte is assumed to be aligned on the least significant bits */
46+
unsigned char lastByte = data[databitlen/8];
47+
/* Concatenate the last few bits provided here with those of the suffix */
48+
unsigned short delimitedLastBytes = (unsigned short)((unsigned short)(lastByte & ((1 << (databitlen % 8)) - 1)) | ((unsigned short)instance->delimitedSuffix << (databitlen % 8)));
49+
if ((delimitedLastBytes & 0xFF00) == 0x0000) {
50+
instance->delimitedSuffix = delimitedLastBytes & 0xFF;
51+
}
52+
else {
53+
unsigned char oneByte[1];
54+
oneByte[0] = delimitedLastBytes & 0xFF;
55+
ret = (HashReturn)KeccakWidth1600_SpongeAbsorb(&instance->sponge, oneByte, 1);
56+
instance->delimitedSuffix = (delimitedLastBytes >> 8) & 0xFF;
57+
}
58+
}
59+
return ret;
60+
}
61+
}
62+
63+
/* ---------------------------------------------------------------- */
64+
65+
HashReturn Keccak_HashFinal(Keccak_HashInstance *instance, BitSequence *hashval)
66+
{
67+
HashReturn ret = (HashReturn)KeccakWidth1600_SpongeAbsorbLastFewBits(&instance->sponge, instance->delimitedSuffix);
68+
if (ret == KECCAK_SUCCESS)
69+
return (HashReturn)KeccakWidth1600_SpongeSqueeze(&instance->sponge, hashval, instance->fixedOutputLength/8);
70+
else
71+
return ret;
72+
}
73+
74+
/* ---------------------------------------------------------------- */
75+
76+
HashReturn Keccak_HashSqueeze(Keccak_HashInstance *instance, BitSequence *data, BitLength databitlen)
77+
{
78+
if ((databitlen % 8) != 0)
79+
return KECCAK_FAIL;
80+
return (HashReturn)KeccakWidth1600_SpongeSqueeze(&instance->sponge, data, databitlen/8);
81+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
The eXtended Keccak Code Package (XKCP)
3+
https://github.com/XKCP/XKCP
4+
5+
Keccak, designed by Guido Bertoni, Joan Daemen, Michaël Peeters and Gilles Van Assche.
6+
7+
Implementation by the designers, hereby denoted as "the implementer".
8+
9+
For more information, feedback or questions, please refer to the Keccak Team website:
10+
https://keccak.team/
11+
12+
To the extent possible under law, the implementer has waived all copyright
13+
and related or neighboring rights to the source code in this file.
14+
http://creativecommons.org/publicdomain/zero/1.0/
15+
*/
16+
17+
#ifndef _KeccakHashInterface_h_
18+
#define _KeccakHashInterface_h_
19+
20+
#include "config.h"
21+
#ifdef XKCP_has_KeccakP1600
22+
23+
#include <stdint.h>
24+
#include <string.h>
25+
#include "KeccakSponge.h"
26+
27+
#ifndef _Keccak_BitTypes_
28+
#define _Keccak_BitTypes_
29+
typedef uint8_t BitSequence;
30+
31+
typedef size_t BitLength;
32+
#endif
33+
34+
typedef enum { KECCAK_SUCCESS = 0, KECCAK_FAIL = 1, KECCAK_BAD_HASHLEN = 2 } HashReturn;
35+
36+
typedef struct {
37+
KeccakWidth1600_SpongeInstance sponge;
38+
unsigned int fixedOutputLength;
39+
unsigned char delimitedSuffix;
40+
} Keccak_HashInstance;
41+
42+
/**
43+
* Function to initialize the Keccak[r, c] sponge function instance used in sequential hashing mode.
44+
* @param hashInstance Pointer to the hash instance to be initialized.
45+
* @param rate The value of the rate r.
46+
* @param capacity The value of the capacity c.
47+
* @param hashbitlen The desired number of output bits,
48+
* or 0 for an arbitrarily-long output.
49+
* @param delimitedSuffix Bits that will be automatically appended to the end
50+
* of the input message, as in domain separation.
51+
* This is a byte containing from 0 to 7 bits
52+
* formatted like the @a delimitedData parameter of
53+
* the Keccak_SpongeAbsorbLastFewBits() function.
54+
* @pre One must have r+c=1600 and the rate a multiple of 8 bits in this implementation.
55+
* @return KECCAK_SUCCESS if successful, KECCAK_FAIL otherwise.
56+
*/
57+
HashReturn Keccak_HashInitialize(Keccak_HashInstance *hashInstance, unsigned int rate, unsigned int capacity, unsigned int hashbitlen, unsigned char delimitedSuffix);
58+
59+
/** Macro to initialize a SHAKE128 instance as specified in the FIPS 202 standard.
60+
*/
61+
#define Keccak_HashInitialize_SHAKE128(hashInstance) Keccak_HashInitialize(hashInstance, 1344, 256, 0, 0x1F)
62+
63+
/** Macro to initialize a SHAKE256 instance as specified in the FIPS 202 standard.
64+
*/
65+
#define Keccak_HashInitialize_SHAKE256(hashInstance) Keccak_HashInitialize(hashInstance, 1088, 512, 0, 0x1F)
66+
67+
/** Macro to initialize a SHA3-224 instance as specified in the FIPS 202 standard.
68+
*/
69+
#define Keccak_HashInitialize_SHA3_224(hashInstance) Keccak_HashInitialize(hashInstance, 1152, 448, 224, 0x06)
70+
71+
/** Macro to initialize a SHA3-256 instance as specified in the FIPS 202 standard.
72+
*/
73+
#define Keccak_HashInitialize_SHA3_256(hashInstance) Keccak_HashInitialize(hashInstance, 1088, 512, 256, 0x06)
74+
75+
/** Macro to initialize a SHA3-384 instance as specified in the FIPS 202 standard.
76+
*/
77+
#define Keccak_HashInitialize_SHA3_384(hashInstance) Keccak_HashInitialize(hashInstance, 832, 768, 384, 0x06)
78+
79+
/** Macro to initialize a SHA3-512 instance as specified in the FIPS 202 standard.
80+
*/
81+
#define Keccak_HashInitialize_SHA3_512(hashInstance) Keccak_HashInitialize(hashInstance, 576, 1024, 512, 0x06)
82+
83+
/**
84+
* Function to give input data to be absorbed.
85+
* @param hashInstance Pointer to the hash instance initialized by Keccak_HashInitialize().
86+
* @param data Pointer to the input data.
87+
* When @a databitLen is not a multiple of 8, the last bits of data must be
88+
* in the least significant bits of the last byte (little-endian convention).
89+
* In this case, the (8 - @a databitLen mod 8) most significant bits
90+
* of the last byte are ignored.
91+
* @param databitLen The number of input bits provided in the input data.
92+
* @pre In the previous call to Keccak_HashUpdate(), databitlen was a multiple of 8.
93+
* @return KECCAK_SUCCESS if successful, KECCAK_FAIL otherwise.
94+
*/
95+
HashReturn Keccak_HashUpdate(Keccak_HashInstance *hashInstance, const BitSequence *data, BitLength databitlen);
96+
97+
/**
98+
* Function to call after all input blocks have been input and to get
99+
* output bits if the length was specified when calling Keccak_HashInitialize().
100+
* @param hashInstance Pointer to the hash instance initialized by Keccak_HashInitialize().
101+
* If @a hashbitlen was not 0 in the call to Keccak_HashInitialize(), the number of
102+
* output bits is equal to @a hashbitlen.
103+
* If @a hashbitlen was 0 in the call to Keccak_HashInitialize(), the output bits
104+
* must be extracted using the Keccak_HashSqueeze() function.
105+
* @param hashval Pointer to the buffer where to store the output data.
106+
* @return KECCAK_SUCCESS if successful, KECCAK_FAIL otherwise.
107+
*/
108+
HashReturn Keccak_HashFinal(Keccak_HashInstance *hashInstance, BitSequence *hashval);
109+
110+
/**
111+
* Function to squeeze output data.
112+
* @param hashInstance Pointer to the hash instance initialized by Keccak_HashInitialize().
113+
* @param data Pointer to the buffer where to store the output data.
114+
* @param databitlen The number of output bits desired (must be a multiple of 8).
115+
* @pre Keccak_HashFinal() must have been already called.
116+
* @pre @a databitlen is a multiple of 8.
117+
* @return KECCAK_SUCCESS if successful, KECCAK_FAIL otherwise.
118+
*/
119+
HashReturn Keccak_HashSqueeze(Keccak_HashInstance *hashInstance, BitSequence *data, BitLength databitlen);
120+
121+
#else
122+
#error This requires an implementation of Keccak-p[1600]
123+
#endif
124+
125+
#endif

0 commit comments

Comments
 (0)