revising aliases ec, encryption

This commit is contained in:
2025-02-08 15:11:54 -01:06
parent 81adc343c0
commit 1875077abb
97 changed files with 895 additions and 1159 deletions

View File

@@ -8,14 +8,15 @@ import (
"strings"
"lukechampine.com/frand"
"realy.lol/hex"
"realy.lol/p256k"
)
// ComputeSharedSecret returns a shared secret key used to encrypt messages. The private and public keys should be hex
// encoded. Uses the Diffie-Hellman key exchange (ECDH) (RFC 4753).
func ComputeSharedSecret(pkh, skh st) (sharedSecret by, err er) {
var skb, pkb by
func ComputeSharedSecret(pkh, skh string) (sharedSecret []byte, err error) {
var skb, pkb []byte
if skb, err = hex.Dec(skh); chk.E(err) {
return
}
@@ -38,9 +39,9 @@ func ComputeSharedSecret(pkh, skh st) (sharedSecret by, err er) {
// Returns: base64(encrypted_bytes) + "?iv=" + base64(initialization_vector).
//
// Deprecated: upgrade to using Decrypt with the NIP-44 algorithm.
func EncryptNip4(msg st, key by) (ct by, err er) {
func EncryptNip4(msg string, key []byte) (ct []byte, err error) {
// block size is 16 bytes
iv := make(by, 16)
iv := make([]byte, 16)
if _, err = frand.Read(iv); chk.E(err) {
err = errorf.E("error creating initialization vector: %w", err)
return
@@ -52,18 +53,18 @@ func EncryptNip4(msg st, key by) (ct by, err er) {
return
}
mode := cipher.NewCBCEncrypter(block, iv)
plaintext := by(msg)
plaintext := []byte(msg)
// add padding
base := len(plaintext)
// this will be a number between 1 and 16 (inclusive), never 0
bs := block.BlockSize()
padding := bs - base%bs
// encode the padding in all the padding bytes themselves
padText := bytes.Repeat(by{byte(padding)}, padding)
padText := bytes.Repeat([]byte{byte(padding)}, padding)
paddedMsgBytes := append(plaintext, padText...)
ciphertext := make(by, len(paddedMsgBytes))
ciphertext := make([]byte, len(paddedMsgBytes))
mode.CryptBlocks(ciphertext, paddedMsgBytes)
return by(base64.StdEncoding.EncodeToString(ciphertext) + "?iv=" +
return []byte(base64.StdEncoding.EncodeToString(ciphertext) + "?iv=" +
base64.StdEncoding.EncodeToString(iv)), nil
}
@@ -71,18 +72,18 @@ func EncryptNip4(msg st, key by) (ct by, err er) {
// EncryptNip4(message, key).
//
// Deprecated: upgrade to using Decrypt with the NIP-44 algorithm.
func DecryptNip4(content st, key by) (msg by, err er) {
func DecryptNip4(content string, key []byte) (msg []byte, err error) {
parts := strings.Split(content, "?iv=")
if len(parts) < 2 {
return nil, errorf.E(
"error parsing encrypted message: no initialization vector")
}
var ciphertext by
var ciphertext []byte
if ciphertext, err = base64.StdEncoding.DecodeString(parts[0]); chk.E(err) {
err = errorf.E("error decoding ciphertext from base64: %w", err)
return
}
var iv by
var iv []byte
if iv, err = base64.StdEncoding.DecodeString(parts[1]); chk.E(err) {
err = errorf.E("error decoding iv from base64: %w", err)
return
@@ -93,7 +94,7 @@ func DecryptNip4(content st, key by) (msg by, err er) {
return
}
mode := cipher.NewCBCDecrypter(block, iv)
msg = make(by, len(ciphertext))
msg = make([]byte, len(ciphertext))
mode.CryptBlocks(msg, ciphertext)
// remove padding
var (
@@ -101,7 +102,7 @@ func DecryptNip4(content st, key by) (msg by, err er) {
)
if plaintextLen > 0 {
// the padding amount is encoded in the padding bytes themselves
padding := no(msg[plaintextLen-1])
padding := int(msg[plaintextLen-1])
if padding > plaintextLen {
err = errorf.E("invalid padding amount: %d", padding)
return

View File

@@ -11,6 +11,7 @@ import (
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/hkdf"
"realy.lol/sha256"
)
@@ -21,14 +22,14 @@ const (
)
type Opts struct {
err er
nonce by
err error
nonce []byte
}
// Deprecated: use WithCustomNonce instead of WithCustomSalt, so the naming is less confusing
var WithCustomSalt = WithCustomNonce
func WithCustomNonce(salt by) func(opts *Opts) {
func WithCustomNonce(salt []byte) func(opts *Opts) {
return func(opts *Opts) {
if len(salt) != 32 {
opts.err = errorf.E("salt must be 32 bytes, got %d", len(salt))
@@ -37,9 +38,9 @@ func WithCustomNonce(salt by) func(opts *Opts) {
}
}
func Encrypt(plaintext st, conversationKey by,
applyOptions ...func(opts *Opts)) (cipherString st,
err er) {
func Encrypt(plaintext string, conversationKey []byte,
applyOptions ...func(opts *Opts)) (cipherString string,
err error) {
var o Opts
for _, apply := range applyOptions {
apply(&o)
@@ -49,34 +50,34 @@ func Encrypt(plaintext st, conversationKey by,
return
}
if o.nonce == nil {
o.nonce = make(by, 32)
o.nonce = make([]byte, 32)
if _, err = rand.Read(o.nonce); chk.E(err) {
return
}
}
var enc, cc20nonce, auth by
var enc, cc20nonce, auth []byte
if enc, cc20nonce, auth, err = getKeys(conversationKey, o.nonce); chk.E(err) {
return
}
plain := by(plaintext)
plain := []byte(plaintext)
size := len(plain)
if size < MinPlaintextSize || size > MaxPlaintextSize {
err = errorf.E("plaintext should be between 1b and 64kB")
return
}
padding := calcPadding(size)
padded := make(by, 2+padding)
padded := make([]byte, 2+padding)
binary.BigEndian.PutUint16(padded, uint16(size))
copy(padded[2:], plain)
var cipher by
var cipher []byte
if cipher, err = encrypt(enc, cc20nonce, padded); chk.E(err) {
return
}
var mac by
var mac []byte
if mac, err = sha256Hmac(auth, cipher, o.nonce); chk.E(err) {
return
}
ct := make(by, 0, 1+32+len(cipher)+32)
ct := make([]byte, 0, 1+32+len(cipher)+32)
ct = append(ct, version)
ct = append(ct, o.nonce...)
ct = append(ct, cipher...)
@@ -85,7 +86,7 @@ func Encrypt(plaintext st, conversationKey by,
return
}
func Decrypt(b64ciphertextWrapped st, conversationKey by) (plaintext st, err er) {
func Decrypt(b64ciphertextWrapped string, conversationKey []byte) (plaintext string, err error) {
cLen := len(b64ciphertextWrapped)
if cLen < 132 || cLen > 87472 {
err = errorf.E("invalid payload length: %d", cLen)
@@ -95,7 +96,7 @@ func Decrypt(b64ciphertextWrapped st, conversationKey by) (plaintext st, err er)
err = errorf.E("unknown version")
return
}
var decoded by
var decoded []byte
if decoded, err = base64.StdEncoding.DecodeString(b64ciphertextWrapped); chk.E(err) {
return
}
@@ -109,11 +110,11 @@ func Decrypt(b64ciphertextWrapped st, conversationKey by) (plaintext st, err er)
return
}
nonce, ciphertext, givenMac := decoded[1:33], decoded[33:dLen-32], decoded[dLen-32:]
var enc, cc20nonce, auth by
var enc, cc20nonce, auth []byte
if enc, cc20nonce, auth, err = getKeys(conversationKey, nonce); chk.E(err) {
return
}
var expectedMac by
var expectedMac []byte
if expectedMac, err = sha256Hmac(auth, ciphertext, nonce); chk.E(err) {
return
}
@@ -121,51 +122,51 @@ func Decrypt(b64ciphertextWrapped st, conversationKey by) (plaintext st, err er)
err = errorf.E("invalid hmac")
return
}
var padded by
var padded []byte
if padded, err = encrypt(enc, cc20nonce, ciphertext); chk.E(err) {
return
}
unpaddedLen := binary.BigEndian.Uint16(padded[0:2])
if unpaddedLen < uint16(MinPlaintextSize) || unpaddedLen > uint16(MaxPlaintextSize) ||
len(padded) != 2+calcPadding(no(unpaddedLen)) {
len(padded) != 2+calcPadding(int(unpaddedLen)) {
err = errorf.E("invalid padding")
return
}
unpadded := padded[2:][:unpaddedLen]
if len(unpadded) == 0 || len(unpadded) != no(unpaddedLen) {
if len(unpadded) == 0 || len(unpadded) != int(unpaddedLen) {
err = errorf.E("invalid padding")
return
}
plaintext = st(unpadded)
plaintext = string(unpadded)
return
}
func GenerateConversationKey(pkh, skh st) (ck by, err er) {
func GenerateConversationKey(pkh, skh string) (ck []byte, err error) {
if skh >= "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" ||
skh == "0000000000000000000000000000000000000000000000000000000000000000" {
err = errorf.E("invalid private key: x coordinate %s is not on the secp256k1 curve",
skh)
return
}
var shared by
var shared []byte
if shared, err = ComputeSharedSecret(pkh, skh); chk.E(err) {
return
}
ck = hkdf.Extract(sha256.New, shared, by("nip44-v2"))
ck = hkdf.Extract(sha256.New, shared, []byte("nip44-v2"))
return
}
func encrypt(key, nonce, message by) (dst by, err er) {
func encrypt(key, nonce, message []byte) (dst []byte, err error) {
var cipher *chacha20.Cipher
if cipher, err = chacha20.NewUnauthenticatedCipher(key, nonce); chk.E(err) {
return
}
dst = make(by, len(message))
dst = make([]byte, len(message))
cipher.XORKeyStream(dst, message)
return
}
func sha256Hmac(key, ciphertext, nonce by) (h by, err er) {
func sha256Hmac(key, ciphertext, nonce []byte) (h []byte, err error) {
if len(nonce) != sha256.Size {
err = errorf.E("nonce aad must be 32 bytes")
return
@@ -177,7 +178,7 @@ func sha256Hmac(key, ciphertext, nonce by) (h by, err er) {
return
}
func getKeys(conversationKey, nonce by) (enc, cc20nonce, auth by, err er) {
func getKeys(conversationKey, nonce []byte) (enc, cc20nonce, auth []byte, err error) {
if len(conversationKey) != 32 {
err = errorf.E("conversation key must be 32 bytes")
return
@@ -187,27 +188,27 @@ func getKeys(conversationKey, nonce by) (enc, cc20nonce, auth by, err er) {
return
}
r := hkdf.Expand(sha256.New, conversationKey, nonce)
enc = make(by, 32)
enc = make([]byte, 32)
if _, err = io.ReadFull(r, enc); chk.E(err) {
return
}
cc20nonce = make(by, 12)
cc20nonce = make([]byte, 12)
if _, err = io.ReadFull(r, cc20nonce); chk.E(err) {
return
}
auth = make(by, 32)
auth = make([]byte, 32)
if _, err = io.ReadFull(r, auth); chk.E(err) {
return
}
return
}
func calcPadding(sLen no) (l no) {
func calcPadding(sLen int) (l int) {
if sLen <= 32 {
return 32
}
nextPower := 1 << no(math.Floor(math.Log2(float64(sLen-1)))+1)
chunk := no(math.Max(32, float64(nextPower/8)))
l = chunk * no(math.Floor(float64((sLen-1)/chunk))+1)
nextPower := 1 << int(math.Floor(math.Log2(float64(sLen-1)))+1)
chunk := int(math.Max(32, float64(nextPower/8)))
l = chunk * int(math.Floor(float64((sLen-1)/chunk))+1)
return
}

View File

@@ -14,12 +14,12 @@ import (
"realy.lol/sha256"
)
func assertCryptPriv(t *testing.T, sk1, sk2, conversationKey, salt, plaintext, expected st) {
func assertCryptPriv(t *testing.T, sk1, sk2, conversationKey, salt, plaintext, expected string) {
var (
k1, s by
actual, decrypted st
ok bo
err er
k1, s []byte
actual, decrypted string
ok bool
err error
)
k1, err = hex.Dec(conversationKey)
if ok = assert.NoErrorf(t, err, "hex decode failed for conversation key: %v", err); !ok {
@@ -46,11 +46,11 @@ func assertCryptPriv(t *testing.T, sk1, sk2, conversationKey, salt, plaintext, e
assert.Equal(t, decrypted, plaintext, "wrong decryption")
}
func assertDecryptFail(t *testing.T, conversationKey, plaintext, ciphertext, msg st) {
func assertDecryptFail(t *testing.T, conversationKey, plaintext, ciphertext, msg string) {
var (
k1 by
ok bo
err er
k1 []byte
ok bool
err error
)
k1, err = hex.Dec(conversationKey)
if ok = assert.NoErrorf(t, err, "hex decode failed for conversation key: %v", err); !ok {
@@ -60,17 +60,17 @@ func assertDecryptFail(t *testing.T, conversationKey, plaintext, ciphertext, msg
assert.ErrorContains(t, err, msg)
}
func assertConversationKeyFail(t *testing.T, priv st, pub st, msg st) {
func assertConversationKeyFail(t *testing.T, priv string, pub string, msg string) {
_, err := GenerateConversationKey(pub, priv)
assert.ErrorContains(t, err, msg)
}
func assertConversationKeyGeneration(t *testing.T, priv, pub, conversationKey st) bo {
func assertConversationKeyGeneration(t *testing.T, priv, pub, conversationKey string) bool {
var (
actualConversationKey,
expectedConversationKey by
ok bo
err er
expectedConversationKey []byte
ok bool
err error
)
expectedConversationKey, err = hex.Dec(conversationKey)
if ok = assert.NoErrorf(t, err, "hex decode failed for conversation key: %v", err); !ok {
@@ -87,7 +87,7 @@ func assertConversationKeyGeneration(t *testing.T, priv, pub, conversationKey st
return true
}
func assertConversationKeyGenerationSec(t *testing.T, sk1, sk2, conversationKey st) bo {
func assertConversationKeyGenerationSec(t *testing.T, sk1, sk2, conversationKey string) bool {
pub2, err := keys.GetPublicKeyHex(sk2)
if ok := assert.NoErrorf(t, err, "failed to derive pubkey from sk2: %v", err); !ok {
return false
@@ -95,17 +95,17 @@ func assertConversationKeyGenerationSec(t *testing.T, sk1, sk2, conversationKey
return assertConversationKeyGeneration(t, sk1, pub2, conversationKey)
}
func assertConversationKeyGenerationPub(t *testing.T, sk, pub, conversationKey st) bo {
func assertConversationKeyGenerationPub(t *testing.T, sk, pub, conversationKey string) bool {
return assertConversationKeyGeneration(t, sk, pub, conversationKey)
}
func assertMessageKeyGeneration(t *testing.T,
conversationKey, salt, chachaKey, chachaSalt, hmacKey st) bo {
conversationKey, salt, chachaKey, chachaSalt, hmacKey string) bool {
var (
convKey, convSalt, actualChaChaKey, expectedChaChaKey, actualChaChaNonce,
expectedChaChaNonce, actualHmacKey, expectedHmacKey by
ok bo
err er
expectedChaChaNonce, actualHmacKey, expectedHmacKey []byte
ok bool
err error
)
convKey, err = hex.Dec(conversationKey)
if ok = assert.NoErrorf(t, err, "hex decode failed for convKey: %v", err); !ok {
@@ -144,14 +144,14 @@ func assertMessageKeyGeneration(t *testing.T,
return true
}
func assertCryptLong(t *testing.T, conversationKey, salt, pattern st, repeat int,
plaintextSha256, payloadSha256 st) {
func assertCryptLong(t *testing.T, conversationKey, salt, pattern string, repeat int,
plaintextSha256, payloadSha256 string) {
var (
convKey, convSalt by
plaintext, actualPlaintextSha256, actualPayload, actualPayloadSha256 st
convKey, convSalt []byte
plaintext, actualPlaintextSha256, actualPayload, actualPayloadSha256 string
h hash.Hash
ok bo
err er
ok bool
err error
)
convKey, err = hex.Dec(conversationKey)
if ok = assert.NoErrorf(t, err, "hex decode failed for convKey: %v", err); !ok {
@@ -166,7 +166,7 @@ func assertCryptLong(t *testing.T, conversationKey, salt, pattern st, repeat int
plaintext += pattern
}
h = sha256.New()
h.Write(by(plaintext))
h.Write([]byte(plaintext))
actualPlaintextSha256 = hex.Enc(h.Sum(nil))
if ok = assert.Equalf(t, plaintextSha256, actualPlaintextSha256,
"invalid plaintext sha256 hash: %v", err); !ok {
@@ -177,7 +177,7 @@ func assertCryptLong(t *testing.T, conversationKey, salt, pattern st, repeat int
return
}
h.Reset()
h.Write(by(actualPayload))
h.Write([]byte(actualPayload))
actualPayloadSha256 = hex.Enc(h.Sum(nil))
if ok = assert.Equalf(t, payloadSha256, actualPayloadSha256,
"invalid payload sha256 hash: %v", err); !ok {
@@ -1142,10 +1142,10 @@ func TestMessageKeyGeneration033(t *testing.T) {
func TestMaxLength(t *testing.T) {
sk1 := keys.GeneratePrivateKey()
sk2 := keys.GeneratePrivateKey()
pub2, _ := keys.GetPublicKeyHex(st(sk2))
salt := make(by, 32)
pub2, _ := keys.GetPublicKeyHex(string(sk2))
salt := make([]byte, 32)
rand.Read(salt)
conversationKey, _ := GenerateConversationKey(pub2, st(sk1))
conversationKey, _ := GenerateConversationKey(pub2, string(sk1))
plaintext := strings.Repeat("a", MaxPlaintextSize)
encrypted, err := Encrypt(plaintext, conversationKey, WithCustomNonce(salt))
if chk.E(err) {
@@ -1153,7 +1153,7 @@ func TestMaxLength(t *testing.T) {
}
assertCryptPub(t,
st(sk1),
string(sk1),
pub2,
fmt.Sprintf("%x", conversationKey),
fmt.Sprintf("%x", salt),
@@ -1162,12 +1162,12 @@ func TestMaxLength(t *testing.T) {
)
}
func assertCryptPub(t *testing.T, sk1, pub2, conversationKey, salt, plaintext, expected st) {
func assertCryptPub(t *testing.T, sk1, pub2, conversationKey, salt, plaintext, expected string) {
var (
k1, s by
actual, decrypted st
ok bo
err er
k1, s []byte
actual, decrypted string
ok bool
err error
)
k1, err = hex.Dec(conversationKey)
if ok = assert.NoErrorf(t, err, "hex decode failed for conversation key: %v", err); !ok {

View File

@@ -1,22 +1,9 @@
package encryption
import (
"bytes"
"realy.lol/context"
"realy.lol/lol"
)
type (
bo = bool
by = []byte
st = string
er = error
no = int
cx = context.T
)
var (
log, chk, errorf = lol.Main.Log, lol.Main.Check, lol.Main.Errorf
equals = bytes.Equal
)