You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
97 lines
3.0 KiB
97 lines
3.0 KiB
package encryption |
|
|
|
import ( |
|
"bytes" |
|
"crypto/aes" |
|
"crypto/cipher" |
|
"encoding/base64" |
|
|
|
"lol.mleku.dev/chk" |
|
"lol.mleku.dev/errorf" |
|
"lukechampine.com/frand" |
|
) |
|
|
|
// EncryptNip4 encrypts message with key using aes-256-cbc. key should be the shared secret generated by |
|
// ComputeSharedSecret. |
|
// |
|
// Returns: base64(encrypted_bytes) + "?iv=" + base64(initialization_vector). |
|
func EncryptNip4(msg, key []byte) (ct []byte, err error) { |
|
// block size is 16 bytes |
|
iv := make([]byte, 16) |
|
if _, err = frand.Read(iv); chk.E(err) { |
|
err = errorf.E("error creating initialization vector: %w", err) |
|
return |
|
} |
|
// automatically picks aes-256 based on key length (32 bytes) |
|
var block cipher.Block |
|
if block, err = aes.NewCipher(key); chk.E(err) { |
|
err = errorf.E("error creating block cipher: %w", err) |
|
return |
|
} |
|
mode := cipher.NewCBCEncrypter(block, iv) |
|
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([]byte{byte(padding)}, padding) |
|
paddedMsgBytes := append(plaintext, padText...) |
|
ciphertext := make([]byte, len(paddedMsgBytes)) |
|
mode.CryptBlocks(ciphertext, paddedMsgBytes) |
|
return []byte(base64.StdEncoding.EncodeToString(ciphertext) + "?iv=" + |
|
base64.StdEncoding.EncodeToString(iv)), nil |
|
} |
|
|
|
// DecryptNip4 decrypts a content string using the shared secret key. The inverse operation to message -> |
|
// EncryptNip4(message, key). |
|
func DecryptNip4(content, key []byte) (msg []byte, err error) { |
|
parts := bytes.Split(content, []byte("?iv=")) |
|
if len(parts) < 2 { |
|
return nil, errorf.E( |
|
"error parsing encrypted message: no initialization vector", |
|
) |
|
} |
|
ciphertextBuf := make([]byte, base64.StdEncoding.EncodedLen(len(parts[0]))) |
|
var ciphertextLen int |
|
if ciphertextLen, err = base64.StdEncoding.Decode(ciphertextBuf, parts[0]); chk.E(err) { |
|
err = errorf.E("error decoding ciphertext from base64: %w", err) |
|
return |
|
} |
|
ciphertext := ciphertextBuf[:ciphertextLen] |
|
|
|
ivBuf := make([]byte, base64.StdEncoding.EncodedLen(len(parts[1]))) |
|
var ivLen int |
|
if ivLen, err = base64.StdEncoding.Decode(ivBuf, parts[1]); chk.E(err) { |
|
err = errorf.E("error decoding iv from base64: %w", err) |
|
return |
|
} |
|
iv := ivBuf[:ivLen] |
|
if len(iv) != 16 { |
|
err = errorf.E("invalid IV length: %d, expected 16", len(iv)) |
|
return |
|
} |
|
var block cipher.Block |
|
if block, err = aes.NewCipher(key); chk.E(err) { |
|
err = errorf.E("error creating block cipher: %w", err) |
|
return |
|
} |
|
mode := cipher.NewCBCDecrypter(block, iv) |
|
msg = make([]byte, len(ciphertext)) |
|
mode.CryptBlocks(msg, ciphertext) |
|
// remove padding |
|
var ( |
|
plaintextLen = len(msg) |
|
) |
|
if plaintextLen > 0 { |
|
// the padding amount is encoded in the padding bytes themselves |
|
padding := int(msg[plaintextLen-1]) |
|
if padding > plaintextLen { |
|
err = errorf.E("invalid padding amount: %d", padding) |
|
return |
|
} |
|
msg = msg[0 : plaintextLen-padding] |
|
} |
|
return msg, nil |
|
}
|
|
|