Implement NIP-44 decrypt

This commit is contained in:
ekzyis 2023-09-30 14:40:52 +02:00
parent 0cbe107a98
commit 154a8f6f8e
2 changed files with 82 additions and 32 deletions

View File

@ -1,6 +1,7 @@
package nip44
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
@ -47,7 +48,7 @@ func Encrypt(key []byte, plaintext string, options *EncryptOptions) (string, err
}
}
if version != 2 {
return "", errors.New("unknown encryption version")
return "", errors.New("unknown version")
}
if len(salt) != 32 {
return "", errors.New("salt must be 32 bytes")
@ -69,6 +70,50 @@ func Encrypt(key []byte, plaintext string, options *EncryptOptions) (string, err
return base64.StdEncoding.EncodeToString(concat), nil
}
func Decrypt(key []byte, ciphertext string) (string, error) {
var (
version int = 2
decoded []byte
dLen int
salt []byte
ciphertext_ []byte
hmac_ []byte
enc []byte
nonce []byte
auth []byte
padded []byte
unpaddedLen uint16
unpadded []byte
err error
)
if ciphertext[0:1] == "#" {
return "", errors.New("unknown version")
}
if decoded, err = base64.StdEncoding.DecodeString(ciphertext); err != nil {
return "", err
}
if version = int(decoded[0]); version != 2 {
return "", errors.New("unknown version")
}
dLen = len(decoded)
salt, ciphertext_, hmac_ = decoded[1:33], decoded[33:dLen-32], decoded[dLen-32:]
if enc, nonce, auth, err = messageKeys(key, salt); err != nil {
return "", err
}
if !bytes.Equal(hmac_, sha256Hmac(auth, ciphertext_)) {
return "", errors.New("invalid hmac")
}
if padded, err = chacha20Encrypt(enc, nonce, ciphertext_); err != nil {
return "", err
}
unpaddedLen = binary.BigEndian.Uint16(padded[0:2])
unpadded = padded[2 : unpaddedLen+2]
if len(unpadded) == 0 || len(unpadded) != int(unpaddedLen) || len(padded) != 2+calcPadding(int(unpaddedLen)) {
return "", errors.New("invalid padding")
}
return string(unpadded), nil
}
func chacha20Encrypt(key []byte, nonce []byte, message []byte) ([]byte, error) {
var (
cipher *chacha20.Cipher

View File

@ -8,12 +8,13 @@ import (
"github.com/stretchr/testify/assert"
)
func assertEncrypt(t *testing.T, key string, salt string, plaintext string, expected string) {
func assertCrypt(t *testing.T, key string, salt string, plaintext string, expected string) {
var (
k []byte
s []byte
actual string
err error
k []byte
s []byte
actual string
decrypted string
err error
)
if k, err = hex.DecodeString(key); err != nil {
t.Errorf("hex decode failed for key")
@ -24,11 +25,15 @@ func assertEncrypt(t *testing.T, key string, salt string, plaintext string, expe
actual, err = nip44.Encrypt(k, plaintext, &nip44.EncryptOptions{Salt: s})
if assert.NoError(t, err) {
assert.Equal(t, expected, actual)
decrypted, err = nip44.Decrypt(k, expected)
if assert.NoError(t, err) {
assert.Equal(t, decrypted, plaintext)
}
}
}
func TestEncrypt001(t *testing.T) {
assertEncrypt(t,
func TestCrypt001(t *testing.T) {
assertCrypt(t,
"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
"0000000000000000000000000000000000000000000000000000000000000001",
"a",
@ -36,8 +41,8 @@ func TestEncrypt001(t *testing.T) {
)
}
func TestEncrypt002(t *testing.T) {
assertEncrypt(t,
func TestCrypt002(t *testing.T) {
assertCrypt(t,
"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
"f00000000000000000000000000000f00000000000000000000000000000000f",
"🍕🫃",
@ -45,8 +50,8 @@ func TestEncrypt002(t *testing.T) {
)
}
func TestEncrypt003(t *testing.T) {
assertEncrypt(t,
func TestCrypt003(t *testing.T) {
assertCrypt(t,
"94da47d851b9c1ed33b3b72f35434f56aa608d60e573e9c295f568011f4f50a4",
"b635236c42db20f021bb8d1cdff5ca75dd1a0cc72ea742ad750f33010b24f73b",
"表ポあA鷗Œé逍Üߪąñ丂㐀𠀀",
@ -54,8 +59,8 @@ func TestEncrypt003(t *testing.T) {
)
}
func TestEncrypt004(t *testing.T) {
assertEncrypt(t,
func TestCrypt004(t *testing.T) {
assertCrypt(t,
"ab99c122d4586cdd5c813058aa543d0e7233545dbf6874fc34a3d8d9a18fbbc3",
"b20989adc3ddc41cd2c435952c0d59a91315d8c5218d5040573fc3749543acaf",
"ability🤝的 ȺȾ",
@ -63,8 +68,8 @@ func TestEncrypt004(t *testing.T) {
)
}
func TestEncrypt005(t *testing.T) {
assertEncrypt(t,
func TestCrypt005(t *testing.T) {
assertCrypt(t,
"a449f2a85c6d3db0f44c64554a05d11a3c0988d645e4b4b2592072f63662f422",
"8d4442713eb9d4791175cb040d98d6fc5be8864d6ec2f89cf0895a2b2b72d1b1",
"pepper👀їжак",
@ -72,8 +77,8 @@ func TestEncrypt005(t *testing.T) {
)
}
func TestEncrypt006(t *testing.T) {
assertEncrypt(t,
func TestCrypt006(t *testing.T) {
assertCrypt(t,
"decde9938ffcb14fa7ff300105eb1bf239469af9baf376e69755b9070ae48c47",
"2180b52ae645fcf9f5080d81b1f0b5d6f2cd77ff3c986882bb549158462f3407",
"( ͡° ͜ʖ ͡°)",
@ -81,8 +86,8 @@ func TestEncrypt006(t *testing.T) {
)
}
func TestEncrypt007(t *testing.T) {
assertEncrypt(t,
func TestCrypt007(t *testing.T) {
assertCrypt(t,
"c6f2fde7aa00208c388f506455c31c3fa07caf8b516d43bf7514ee19edcda994",
"e4cd5f7ce4eea024bc71b17ad456a986a74ac426c2c62b0a15eb5c5c8f888b68",
"مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ،",
@ -90,8 +95,8 @@ func TestEncrypt007(t *testing.T) {
)
}
func TestEncrypt008(t *testing.T) {
assertEncrypt(t,
func TestCrypt008(t *testing.T) {
assertCrypt(t,
"c6f2fde7aa00208c388f506455c31c3fa07caf8b516d43bf7514ee19edcda994",
"38d1ca0abef9e5f564e89761a86cee04574b6825d3ef2063b10ad75899e4b023",
"الكل في المجمو عة (5)",
@ -99,8 +104,8 @@ func TestEncrypt008(t *testing.T) {
)
}
func TestEncrypt009(t *testing.T) {
assertEncrypt(t,
func TestCrypt009(t *testing.T) {
assertCrypt(t,
"c6f2fde7aa00208c388f506455c31c3fa07caf8b516d43bf7514ee19edcda994",
"4f1a31909f3483a9e69c8549a55bbc9af25fa5bbecf7bd32d9896f83ef2e12e0",
"𝖑𝖆𝖟𝖞 社會科學院語學研究所",
@ -108,8 +113,8 @@ func TestEncrypt009(t *testing.T) {
)
}
func TestEncrypt010(t *testing.T) {
assertEncrypt(t,
func TestCrypt010(t *testing.T) {
assertCrypt(t,
"c6f2fde7aa00208c388f506455c31c3fa07caf8b516d43bf7514ee19edcda994",
"a3e219242d85465e70adcd640b564b3feff57d2ef8745d5e7a0663b2dccceb54",
"🙈 🙉 🙊 0⃣ 1⃣ 2⃣ 3⃣ 4⃣ 5⃣ 6⃣ 7⃣ 8⃣ 9⃣ 🔟 Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗",
@ -117,24 +122,24 @@ func TestEncrypt010(t *testing.T) {
)
}
func TestEncrypt011(t *testing.T) {
assertEncrypt(t,
func TestCrypt011(t *testing.T) {
assertCrypt(t,
"7a1ccf5ce5a08e380f590de0c02776623b85a61ae67cfb6a017317e505b7cb51",
"a000000000000000000000000000000000000000000000000000000000000001",
"⁰⁴⁵₀₁₂",
"AqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2+xmGnjIMPMqqJGmjdYAYZUDUyEEUO3/evHUaO40LePeR91VlMVZ7I+nKJPkaUiKZ3cQiQnA86Uwti2IxepmzOFN",
)
}
func TestEncrypt012(t *testing.T) {
assertEncrypt(t,
func TestCrypt012(t *testing.T) {
assertCrypt(t,
"aa971537d741089885a0b48f2730a125e15b36033d089d4537a4e1204e76b39e",
"b000000000000000000000000000000000000000000000000000000000000002",
"A Peer-to-Peer Electronic Cash System",
"ArAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACyuqG6RycuPyDPtwxzTcuMQu+is3N5XuWTlvCjligVaVBRydexaylXbsX592MEd3/Jt13BNL/GlpYpGDvLS4Tt/+2s9FX/16e/RDc+czdwXglc4DdSHiq+O06BvvXYfEQOPw=",
)
}
func TestEncrypt013(t *testing.T) {
assertEncrypt(t,
func TestCrypt013(t *testing.T) {
assertCrypt(t,
"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution. Digital signatures provide part of the solution, but the main benefits are lost if a trusted third party is still required to prevent double-spending.",