mirror of
https://github.com/henrydcase/nobs.git
synced 2024-11-22 23:28:57 +00:00
218 lines
6.1 KiB
Go
218 lines
6.1 KiB
Go
// [SIKE] http://www.sike.org/files/SIDH-spec.pdf
|
|
// [REF] https://github.com/Microsoft/PQCrypto-SIDH
|
|
package sike
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"errors"
|
|
"io"
|
|
// TODO: Use implementation from xcrypto, once PR below merged
|
|
// https://go-review.googlesource.com/c/crypto/+/111281/
|
|
. "github.com/henrydcase/nobs/dh/sidh"
|
|
cshake "github.com/henrydcase/nobs/hash/sha3"
|
|
)
|
|
|
|
// Constants used for cSHAKE customization
|
|
// Those values are different than in [SIKE] - they are encoded on 16bits. This is
|
|
// done in order for implementation to be compatible with [REF] and test vectors.
|
|
var G = []byte{0x00, 0x00}
|
|
var H = []byte{0x01, 0x00}
|
|
var F = []byte{0x02, 0x00}
|
|
|
|
// Generates cShake-256 sum
|
|
func cshakeSum(out, in, S []byte) {
|
|
h := cshake.NewCShake256(nil, S)
|
|
h.Write(in)
|
|
h.Read(out)
|
|
}
|
|
|
|
func encrypt(skA *PrivateKey, pkA, pkB *PublicKey, ptext []byte) ([]byte, error) {
|
|
var n [40]byte // n can is max 320-bit (see 1.4 of [SIKE])
|
|
var ptextLen = len(ptext)
|
|
|
|
if pkB.Variant() != KeyVariant_SIKE {
|
|
return nil, errors.New("wrong key type")
|
|
}
|
|
|
|
j, err := DeriveSecret(skA, pkB)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cshakeSum(n[:ptextLen], j, F)
|
|
for i, _ := range ptext {
|
|
n[i] ^= ptext[i]
|
|
}
|
|
|
|
ret := make([]byte, pkA.Size()+ptextLen)
|
|
copy(ret, pkA.Export())
|
|
copy(ret[pkA.Size():], n[:ptextLen])
|
|
return ret, nil
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// PKE interface
|
|
//
|
|
|
|
// Uses SIKE public key to encrypt plaintext. Requires cryptographically secure PRNG
|
|
// Returns ciphertext in case encryption succeeds. Returns error in case PRNG fails
|
|
// or wrongly formated input was provided.
|
|
func Encrypt(rng io.Reader, pub *PublicKey, ptext []byte) ([]byte, error) {
|
|
var params = pub.Params()
|
|
var ptextLen = uint(len(ptext))
|
|
// c1 must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3)
|
|
if ptextLen != (params.KemSize + 8) {
|
|
return nil, errors.New("Unsupported message length")
|
|
}
|
|
|
|
skA := NewPrivateKey(params.Id, KeyVariant_SIDH_A)
|
|
err := skA.Generate(rng)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pkA := skA.GeneratePublicKey()
|
|
return encrypt(skA, pkA, pub, ptext)
|
|
}
|
|
|
|
// Uses SIKE private key to decrypt ciphertext. Returns plaintext in case
|
|
// decryption succeeds or error in case unexptected input was provided.
|
|
// Constant time
|
|
func Decrypt(prv *PrivateKey, ctext []byte) ([]byte, error) {
|
|
var params = prv.Params()
|
|
var n [40]byte // n can is max 320-bit (see 1.4 of [SIKE])
|
|
var c1_len int
|
|
var pk_len = params.PublicKeySize
|
|
|
|
if prv.Variant() != KeyVariant_SIKE {
|
|
return nil, errors.New("wrong key type")
|
|
}
|
|
|
|
// ctext is a concatenation of (pubkey_A || c1=ciphertext)
|
|
// it must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3)
|
|
c1_len = len(ctext) - pk_len
|
|
if c1_len != (int(params.KemSize) + 8) {
|
|
return nil, errors.New("wrong size of cipher text")
|
|
}
|
|
|
|
c0 := NewPublicKey(params.Id, KeyVariant_SIDH_A)
|
|
err := c0.Import(ctext[:pk_len])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
j, err := DeriveSecret(prv, c0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cshakeSum(n[:c1_len], j, F)
|
|
for i, _ := range n[:c1_len] {
|
|
n[i] ^= ctext[pk_len+i]
|
|
}
|
|
|
|
return n[:c1_len], nil
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// KEM interface
|
|
//
|
|
|
|
// Encapsulation receives the public key and generates SIKE ciphertext and shared secret.
|
|
// The generated ciphertext is used for authentication.
|
|
// The rng must be cryptographically secure PRNG.
|
|
// Error is returned in case PRNG fails or wrongly formated input was provided.
|
|
func Encapsulate(rng io.Reader, pub *PublicKey) (ctext []byte, secret []byte, err error) {
|
|
var params = pub.Params()
|
|
// Buffer for random, secret message
|
|
var ptext = make([]byte, params.MsgLen)
|
|
// r = G(ptext||pub)
|
|
var r = make([]byte, params.A.SecretByteLen)
|
|
// Resulting shared secret
|
|
secret = make([]byte, params.KemSize)
|
|
|
|
// Generate ephemeral value
|
|
_, err = io.ReadFull(rng, ptext)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
h := cshake.NewCShake256(nil, G)
|
|
h.Write(ptext)
|
|
h.Write(pub.Export())
|
|
h.Read(r)
|
|
|
|
// cSHAKE256 implementation is byte oriented. Ensure bitlength is not bigger then to 2^e2-1
|
|
r[len(r)-1] &= (1 << (params.A.SecretBitLen % 8)) - 1
|
|
|
|
// (c0 || c1) = Enc(pkA, ptext; r)
|
|
skA := NewPrivateKey(params.Id, KeyVariant_SIDH_A)
|
|
err = skA.Import(r)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
pkA := skA.GeneratePublicKey()
|
|
ctext, err = encrypt(skA, pkA, pub, ptext)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// K = H(ptext||(c0||c1))
|
|
h = cshake.NewCShake256(nil, H)
|
|
h.Write(ptext)
|
|
h.Write(ctext)
|
|
h.Read(secret)
|
|
|
|
return ctext, secret, nil
|
|
}
|
|
|
|
// Decapsulate given the keypair and ciphertext as inputs, Decapsulate outputs a shared
|
|
// secret if plaintext verifies correctly, otherwise function outputs random value.
|
|
// Decapsulation may fail in case input is wrongly formated.
|
|
// Constant time for properly initialized input.
|
|
func Decapsulate(prv *PrivateKey, pub *PublicKey, ctext []byte) ([]byte, error) {
|
|
var params = pub.Params()
|
|
var r = make([]byte, params.A.SecretByteLen)
|
|
// Resulting shared secret
|
|
var secret = make([]byte, params.KemSize)
|
|
var skA = NewPrivateKey(params.Id, KeyVariant_SIDH_A)
|
|
|
|
m, err := Decrypt(prv, ctext)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// r' = G(m'||pub)
|
|
h := cshake.NewCShake256(nil, G)
|
|
h.Write(m)
|
|
h.Write(pub.Export())
|
|
h.Read(r)
|
|
|
|
// cSHAKE256 implementation is byte oriented: Ensure bitlength is not bigger than 2^e2-1
|
|
r[len(r)-1] &= (1 << (params.A.SecretBitLen % 8)) - 1
|
|
|
|
// Never fails
|
|
skA.Import(r)
|
|
|
|
// Never fails
|
|
pkA := skA.GeneratePublicKey()
|
|
c0 := pkA.Export()
|
|
|
|
h = cshake.NewCShake256(nil, H)
|
|
if subtle.ConstantTimeCompare(c0, ctext[:len(c0)]) == 1 {
|
|
h.Write(m)
|
|
} else {
|
|
// S is chosen at random when generating a key and unknown to other party. It
|
|
// may seem weird, but it's correct. It is important that S is unpredictable
|
|
// to other party. Without this check, it is possible to recover a secret, by
|
|
// providing series of invalid ciphertexts. It is also important that in case
|
|
//
|
|
// See more details in "On the security of supersingular isogeny cryptosystems"
|
|
// (S. Galbraith, et al., 2016, ePrint #859).
|
|
h.Write(prv.S)
|
|
}
|
|
h.Write(ctext)
|
|
h.Read(secret)
|
|
return secret, nil
|
|
}
|