Verified Commit eca887af authored by Nadim Kobeissi's avatar Nadim Kobeissi 💾
Browse files

Push to GitHub

parents
# SPDX-FileCopyrightText: © 2020-2021 Nadim Kobeissi <nadim@symbolic.software>
# SPDX-License-Identifier: CC0-1.0
# Verifpal
cmd/kyber-r2d2/resource.syso
build/kyber-r2d2.*
dist
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
*.upx
*.test
# macOS
.DS_Store
# Visual Studio Code
.vscode
# Snapcraft
snapcraft.login
# Coq
*.glob
*.vo
*.vok
*.vos
*.aux
# Scripts
scripts
# C
*.so
*.o
test_kex1024
test_kex512
test_kex768
test_kyber1024
test_kyber512
test_kyber768
test_speed1024
test_speed512
test_speed768
test_vectors1024
test_vectors512
test_vectors768
\ No newline at end of file
run:
skip-files:
- main_test.go
- help.go
- libpeg.go
- libcoq.go
linters:
enable:
- govet
- staticcheck
- gosimple
- gofmt
- structcheck
- varcheck
- ineffassign
- typecheck
- depguard
- dogsled
- dupl
- funlen
- gochecknoinits
- godox
- gocritic
- gocyclo
- gosec
- lll
- misspell
- nakedret
- prealloc
- scopelint
- unconvert
- unparam
- unused
- deadcode
- errcheck
linters-settings:
gocritic:
enabled-checks:
- appendAssign
- caseOrder
- dupArg
- dupBranchBody
- dupCase
- dupSubExpr
- flagDeref
- captLocal
- defaultCaseOrder
- elseif
- ifElseChain
- regexpMust
- sloppyLen
- switchTrue
- typeSwitchVar
- underef
- unlambda
- unslice
- argOrder
- badCall
- badCond
- evalOrder
- exitAfterDefer
- flagName
- mapKey
- nilValReturn
- octalLiteral
- offBy1
- regexpPattern
- sloppyReassign
- truncateCmp
- weakCond
- boolExprSimplify
- builtinShadow
- dupImport
- methodExprCall
- initClause
- newDeref
- nestingReduce
- stringXbytes
- unlabelStmt
- typeUnparen
- unnecessaryBlock
- valSwap
- wrapperFunc
- yodaStyleExpr
goconst:
min-len: 12
funlen:
statements: 64
lines: 128
gocyclo:
min-complexity: 15
govet:
enable-all: true
lll:
line-length: 120
\ No newline at end of file
MIT License
Copyright (c) 2020 Nadim Kobeissi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# SPDX-FileCopyrightText: © 2020-2021 Nadim Kobeissi <nadim@symbolic.software>
# SPDX-License-Identifier: MIT
lint:
@/bin/echo "[Kyber-K2SO] Running golangci-lint..."
@golangci-lint run
test:
@go clean -testcache
@/bin/echo "[Kyber-K2SO] Running test battery..."
@go test ./cmd/kyberk2so
clean:
@/bin/echo -n "[Kyber-K2SO] Cleaning up..."
@$(RM) build/kyberk2so.*
@$(RM) -r dist
@/bin/echo " OK"
.PHONY: all windows linux macos freebsd lint test release clean assets build cmd
<img src="assets/kyber-k2so.png" align="right" height="300" width="300"/>
# Kyber-K2SO
[![GoDoc](https://godoc.org/github.com/symbolicsoft/kyber-k2so?status.svg)](https://pkg.go.dev/github.com/symbolicsoft/kyber-k2so?tab=overview)
[![Go Report Card](https://goreportcard.com/badge/github.com/symbolicsoft/kyber-k2so)](https://goreportcard.com/report/github.com/symbolicsoft/kyber-k2so)
**Kyber-K2SO** is a clean implementation of the [Kyber](https://pq-crystals.org/kyber) IND-CCA2-secure key encapsulation mechanism (KEM), whose security is based on the hardness of solving the learning-with-errors (LWE) problem over module lattices. Kyber is one of the candidate algorithms submitted to the [NIST post-quantum cryptography project](https://csrc.nist.gov/Projects/Post-Quantum-Cryptography).
Kyber-K2SO implements only Kyber-768, and does not provide Kyber-512, Kyber-1024, or the _"90s Kyber"_ variants, because there does not appear to be a convincing reason to ever do so.
## Security Disclaimer
🚨 This library is offered as-is, and without a guarantee. Therefore, it is expected that changes in the code, repository, and API occur in the future. It is recommended to take caution before using this library in a production application since part of its content is experimental.
# Features & Usage
Keeping in mind the Security Disclaimer above, Kyber-K2SO appears to be appropriate for use in any environment supported by Go: client-side application, server-side applications and more. All operations take no more than a few milliseconds on regular computing hardware.
## Features
* 🟢 **Purely functional, easy to read code.** Code readability and predictability is prioritized over performance.
* 🟢 **Smallest codebase.** Kyber-K2SO is to our knowledge the smallest implementation of Kyber Version 2, and is 4.3 times smaller than the reference implementation.
* 🟢 **Simple API.** `KemKeypair()` to generate a private key and a public key, `KemEncrypt(publicKey)` generate and encrypt a shared secret, and `KemDecrypt(ciphertext, privateKey)` to decrypt the shared secret.
* 🟢 **Good performance.** Kyber-K2SO is more than fast enough for regular usage in any environment supported by the Go programming language.
* 🟢 **Constant time (probably).** As far as we can tell, decryption appears to perform in constant time. Further analysis is encouraged.
## Using Kyber-K2SO
```bash
go get -u github.com/symbolicsoft/kyber-k2so
```
```go
privateKeyB, publicKeyB, _ := KemKeypair()
ciphertextA, sharedSecretA, _ := KemEncrypt(publicKeyB)
sharedSecretB, _ := KemDecrypt(ciphertextA, privateKeyB)
```
Yes, it's that simple!
# About Kyber-K2SO
Kyber-K2SO is published by [Symbolic Software](https://symbolic.software) under the MIT License. It is written by [Nadim Kobeissi](https://nadim.computer).
We thank [Peter Schwabe](https://cryptojedi.org/peter) for his feedback during the development of Kyber-K2SO.
\ No newline at end of file
/* SPDX-FileCopyrightText: © 2020-2021 Nadim Kobeissi <nadim@symbolic.software>
* SPDX-License-Identifier: MIT */
package kyberk2so
func byteopsLoad32(x []byte) uint32 {
var r uint32
r = uint32(x[0])
r = r | (uint32(x[1]) << 8)
r = r | (uint32(x[2]) << 16)
r = r | (uint32(x[3]) << 24)
return r
}
func byteopsCbd(buf []byte) poly {
var t, d uint32
var a, b int16
r := polyNew()
for i := 0; i < params.n/8; i++ {
t = byteopsLoad32(buf[4*i : (4*i)+4])
d = t & 0x55555555
d = d + ((t >> 1) & 0x55555555)
for j := 0; j < 8; j++ {
a = int16((d >> (4*j + 0)) & 0x3)
b = int16((d >> (4*j + 2)) & 0x3)
r.coeffs[8*i+j] = a - b
}
}
return r
}
func byteopsMontgomeryReduce(a int32) int16 {
u := int16(a * int32(params.qinv))
t := int32(u) * int32(params.q)
t = a - t
t >>= 16
return int16(t)
}
func byteopsBarrettReduce(a int16) int16 {
var t int16
var v int16 = int16(((uint32(1) << 26) + uint32(params.q/2)) / uint32(params.q))
t = int16(int32(v) * int32(a) >> 26)
t = t * int16(params.q)
return a - t
}
func byteopsCSubQ(a int16) int16 {
a = a - int16(params.q)
a = a + ((a >> 15) & int16(params.q))
return a
}
/* SPDX-FileCopyrightText: © 2020-2021 Nadim Kobeissi <nadim@symbolic.software>
* SPDX-License-Identifier: MIT */
package kyberk2so
import (
"crypto/rand"
"golang.org/x/crypto/sha3"
)
func indcpaPackPublicKey(publicKey polyvec, seed []byte) []byte {
return append(polyvecToBytes(publicKey), seed...)
}
func indcpaUnpackPublicKey(packedPublicKey []byte) (polyvec, []byte) {
publicKeyPolyvec := polyvecFromBytes(packedPublicKey[:params.polyvecbytes])
seed := packedPublicKey[params.polyvecbytes:]
return publicKeyPolyvec, seed
}
func indcpaPackPrivateKey(privateKey polyvec) []byte {
return polyvecToBytes(privateKey)
}
func indcpaUnpackPrivateKey(packedPrivateKey []byte) polyvec {
return polyvecFromBytes(packedPrivateKey)
}
func indcpaPackCiphertext(b polyvec, v poly) []byte {
return append(polyvecCompress(b), polyCompress(v)...)
}
func indcpaUnpackCiphertext(c []byte) (polyvec, poly) {
b := polyvecDecompress(c[:params.polyveccompressedbytes])
v := polyDecompress(c[params.polyveccompressedbytes:])
return b, v
}
func indcpaRejUniform(l int, buf []byte, bufl int) ([]int16, int) {
r := make([]int16, l)
var val uint16
ctr := 0
pos := 0
for ctr < l && pos+2 <= bufl {
val = uint16(buf[pos]) | (uint16(buf[pos+1]) << 8)
pos = pos + 2
if val < uint16(19*params.q) {
val = val - ((val >> 12) * uint16(params.q))
r[ctr] = int16(val)
ctr = ctr + 1
}
}
return r, ctr
}
func indcpaGenMatrix(seed []byte, transposed bool) ([]polyvec, error) {
r := make([]polyvec, params.k)
buf := make([]byte, 4*168)
xof := sha3.NewShake128()
ctr := 0
for i := 0; i < params.k; i++ {
r[i] = polyvecNew()
for j := 0; j < params.k; j++ {
transposon := []byte{byte(j), byte(i)}
if transposed {
transposon = []byte{byte(i), byte(j)}
}
xof.Reset()
_, err := xof.Write(append(seed, transposon...))
if err != nil {
return []polyvec{}, err
}
_, err = xof.Read(buf)
if err != nil {
return []polyvec{}, err
}
r[i].vec[j].coeffs, ctr = indcpaRejUniform(params.n, buf, len(buf))
for ctr < params.n {
bufn := make([]byte, 168)
_, err = xof.Read(bufn)
if err != nil {
return []polyvec{}, err
}
missing, ctrn := indcpaRejUniform(params.n-ctr, bufn, 168)
r[i].vec[j].coeffs = append(
r[i].vec[j].coeffs[:ctr],
missing[:params.n-ctr]...,
)
ctr = ctr + ctrn
}
}
}
return r, nil
}
func indcpaPrf(l int, key []byte, nonce byte) []byte {
hash := make([]byte, l)
sha3.ShakeSum256(hash, append(key, nonce))
return hash
}
func indcpaKeypair() ([]byte, []byte, error) {
skpv := polyvecNew()
pkpv := polyvecNew()
e := polyvecNew()
buf := make([]byte, 2*params.symbytes)
h := sha3.New512()
_, err := rand.Read(buf[:params.symbytes])
if err != nil {
return []byte{}, []byte{}, err
}
_, err = h.Write(buf[:params.symbytes])
if err != nil {
return []byte{}, []byte{}, err
}
buf = buf[:0]
buf = h.Sum(buf)
publicSeed, noiseSeed := buf[:params.symbytes], buf[params.symbytes:]
a, err := indcpaGenMatrix(publicSeed, false)
if err != nil {
return []byte{}, []byte{}, err
}
var nonce byte
for i := 0; i < params.k; i++ {
skpv.vec[i] = polyGetNoise(noiseSeed, nonce)
nonce = nonce + 1
}
for i := 0; i < params.k; i++ {
e.vec[i] = polyGetNoise(noiseSeed, nonce)
nonce = nonce + 1
}
skpv = polyvecNtt(skpv)
e = polyvecNtt(e)
for i := 0; i < params.k; i++ {
pkpv.vec[i] = polyvecPointWiseAccMontgomery(a[i], skpv)
pkpv.vec[i] = polyToMont(pkpv.vec[i])
}
pkpv = polyvecAdd(pkpv, e)
pkpv = polyvecReduce(pkpv)
return indcpaPackPrivateKey(skpv), indcpaPackPublicKey(pkpv, publicSeed), nil
}
func indcpaEncrypt(m []byte, publicKey []byte, coins []byte) ([]byte, error) {
sp := polyvecNew()
ep := polyvecNew()
bp := polyvecNew()
nonce := byte(0)
publicKeyPolyvec, seed := indcpaUnpackPublicKey(publicKey)
k := polyFromMsg(m)
at, err := indcpaGenMatrix(seed[:params.symbytes], true)
if err != nil {
return []byte{}, err
}
for i := 0; i < params.k; i++ {
sp.vec[i] = polyGetNoise(coins, nonce)
nonce = nonce + 1
}
for i := 0; i < params.k; i++ {
ep.vec[i] = polyGetNoise(coins, nonce)
nonce = nonce + 1
}
epp := polyGetNoise(coins, nonce)
sp = polyvecNtt(sp)
for i := 0; i < params.k; i++ {
bp.vec[i] = polyvecPointWiseAccMontgomery(at[i], sp)
}
v := polyvecPointWiseAccMontgomery(publicKeyPolyvec, sp)
bp = polyvecInvNttToMont(bp)
v = polyInvNttToMont(v)
bp = polyvecAdd(bp, ep)
v = polyAdd(v, epp)
v = polyAdd(v, k)
bp = polyvecReduce(bp)
v = polyReduce(v)
return indcpaPackCiphertext(bp, v), nil
}
func indcpaDecrypt(c []byte, privateKey []byte) []byte {
bp, v := indcpaUnpackCiphertext(c)
privateKeyPolyvec := indcpaUnpackPrivateKey(privateKey)
bp = polyvecNtt(bp)
mp := polyvecPointWiseAccMontgomery(privateKeyPolyvec, bp)
mp = polyInvNttToMont(mp)
mp = polySub(v, mp)
mp = polyReduce(mp)
return polyToMsg(mp)
}
/* SPDX-FileCopyrightText: © 2020-2021 Nadim Kobeissi <nadim@symbolic.software>
* SPDX-License-Identifier: MIT */
// Package kyberk2so providesis a clean implementation of the Kyber IND-CCA2-secure
// key encapsulation mechanism (KEM), whose security is based on the hardness of
// solving the learning-with-errors (LWE) problem over module lattices.
package kyberk2so
import (
"crypto/rand"
"crypto/subtle"
"golang.org/x/crypto/sha3"
)
// KemKeypair returns a Kyber-768 private key and a corresponding Kyber-768 public key.
// An accompanying error is returned if no sufficient randomness could be obtained from the system.
func KemKeypair() ([]byte, []byte, error) {
indcpaPrivateKey, indcpaPublicKey, err := indcpaKeypair()
if err != nil {
return []byte{}, []byte{}, err
}
pkh := sha3.Sum256(indcpaPublicKey)
rnd := make([]byte, params.symbytes)
_, err = rand.Read(rnd)
if err != nil {
return []byte{}, []byte{}, err
}
privateKey := append(indcpaPrivateKey, indcpaPublicKey...)
privateKey = append(privateKey, pkh[:]...)
privateKey = append(privateKey, rnd...)
return privateKey, indcpaPublicKey, nil
}
// KemEncrypt takes a public key (from KemKeypair) as input and returns a ciphertext and a 32-byte shared secret.
// An accompanying error is returned if no sufficient randomness could be obtained from the system.
func KemEncrypt(publicKey []byte) ([]byte, []byte, error) {
sharedSecret := make([]byte, params.symbytes)
buf := make([]byte, 2*params.symbytes)
_, err := rand.Read(buf[:params.symbytes])
if err != nil {
return []byte{}, []byte{}, err
}
buf1 := sha3.Sum256(buf[:params.symbytes])
buf2 := sha3.Sum256(publicKey)
kr := sha3.Sum512(append(buf1[:], buf2[:]...))
ciphertext, err := indcpaEncrypt(buf1[:], publicKey, kr[params.symbytes:])
krc := sha3.Sum256(ciphertext)
sha3.ShakeSum256(sharedSecret, append(kr[:params.symbytes], krc[:]...))
return ciphertext, sharedSecret, err
}
// KemDecrypt takes a ciphertext (from KeyEncrypt), a private key (from KemKeypair) and returns a 32-byte shared secret.
// An accompanying error is returned if no sufficient randomness could be obtained from the system.
func KemDecrypt(ciphertext []byte, privateKey []byte) ([]byte, error) {
sharedSecret := make([]byte, params.symbytes)
indcpaPrivateKey := privateKey[:params.indcpasecretkeybytes]
pki := params.indcpasecretkeybytes + params.indcpapublickeybytes
publicKey := privateKey[params.indcpasecretkeybytes:pki]
buf := indcpaDecrypt(ciphertext, indcpaPrivateKey)
ski := params.secretkeybytes - 2*params.symbytes
kr := sha3.Sum512(append(buf, privateKey[ski:ski+params.symbytes]...))
cmp, err := indcpaEncrypt(buf, publicKey, kr[params.symbytes:])
fail := byte(1 - subtle.ConstantTimeCompare(ciphertext, cmp))
krh := sha3.Sum256(ciphertext)
for i := 0; i < params.symbytes; i++ {
skx := privateKey[:params.secretkeybytes-params.symbytes+i]
kr[i] = kr[i] ^ (fail & (kr[i] ^ skx[i]))
}
sha3.ShakeSum256(sharedSecret, append(kr[:params.symbytes], krh[:]...))
return sharedSecret, err
}
/* SPDX-FileCopyrightText: © 2020-2021 Nadim Kobeissi <nadim@symbolic.software>
* SPDX-License-Identifier: MIT */
package kyberk2so
import (
"crypto/subtle"
"testing"
)
func TestMain(t *testing.T) {
for i := 0; i < 30000; i++ {
privateKeyB, publicKeyB, err := KemKeypair()
if err != nil {
t.Error(err)
}
ctA, ssA, err := KemEncrypt(publicKeyB)
if err != nil {
t.Error(err)
}
ssB, err := KemDecrypt(ctA, privateKeyB)
if err != nil {
t.Error(err)
}
if subtle.ConstantTimeCompare(ssA, ssB) == 0 {
t.Error("shared secret failed")
}
}
}
/* SPDX-FileCopyrightText: © 2020-2021 Nadim Kobeissi <nadim@symbolic.software>
* SPDX-License-Identifier: MIT */
package kyberk2so
var nttZetas [128]int16 = [128]int16{
2285, 2571, 2970, 1812, 1493, 1422, 287, 202, 3158, 622, 1577, 182, 962,
2127, 1855, 1468, 573, 2004, 264, 383, 2500, 1458, 1727, 3199, 2648, 1017,
732, 608, 1787, 411, 3124, 1758, 1223, 652, 2777, 1015, 2036, 1491, 3047,
1785, 516, 3321, 3009, 2663, 1711, 2167, 126, 1469, 2476, 3239, 3058, 830,
107, 1908, 3082, 2378, 2931, 961, 1821, 2604, 448, 2264, 677, 2054, 2226,
430, 555, 843, 2078, 871, 1550, 105, 422, 587, 177, 3094, 3038, 2869, 1574,
1653, 3083, 778, 1159, 3182, 2552, 1483, 2727, 1119, 1739, 644, 2457, 349,
418, 329, 3173, 3254, 817, 1097, 603, 610, 1322, 2044, 1864, 384, 2114, 3193,
1218, 1994, 2455, 220, 2142, 1670, 2144, 1799, 2051, 794, 1819, 2475, 2459,
478, 3221, 3021, 996, 991, 958, 1869, 1522, 1628,
}
var nttZetasInv [128]int16 = [128]int16{
1701, 1807, 1460, 2371, 2338, 2333, 308, 108, 2851, 870, 854, 1510, 2535,
1278, 1530, 1185, 1659, 1187, 3109, 874, 1335, 2111, 136, 1215, 2945, 1465,
1285, 2007, 2719, 2726, 2232, 2512, 75, 156, 3000, 2911, 2980, 872, 2685,
1590, 2210, 602, 1846, 777, 147, 2170, 2551, 246, 1676, 1755, 460, 291, 235,
3152, 2742, 2907, 3224, 1779, 2458, 1251, 2486, 2774, 2899, 1103, 1275, 2652,
1065, 2881, 725, 1508, 2368, 398, 951, 247, 1421, 3222, 2499, 271, 90, 853,
1860, 3203, 1162, 1618, 666, 320, 8, 2813, 1544, 282, 1838, 1293, 2314, 552,
2677, 2106, 1571, 205, 2918, 1542, 2721, 2597, 2312, 681, 130, 1602, 1871,
829, 2946, 3065, 1325, 2756, 1861, 1474, 1202, 2367, 3147, 1752, 2707, 171,
3127, 3042, 1907, 1836, 1517, 359, 758, 1441,