115 lines
3.6 KiB
Go
115 lines
3.6 KiB
Go
package crypto
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/minio/blake2b-simd"
|
|
"golang.org/x/crypto/argon2"
|
|
)
|
|
|
|
const (
|
|
loginSubKeyLen = 32
|
|
loginSubKeyId = 1
|
|
loginSubKeyContext = "loginctx"
|
|
|
|
decryptionBufferSize = 4 * 1024 * 1024
|
|
)
|
|
const (
|
|
cryptoKDFBlake2bBytesMin = 16
|
|
cryptoKDFBlake2bBytesMax = 64
|
|
cryptoGenerichashBlake2bSaltBytes = 16
|
|
cryptoGenerichashBlake2bPersonalBytes = 16
|
|
BoxSealBytes = 48 // 32 for the ephemeral public key + 16 for the MAC
|
|
)
|
|
|
|
var (
|
|
ErrOpenBox = errors.New("failed to open box")
|
|
ErrSealedOpenBox = errors.New("failed to open sealed box")
|
|
)
|
|
|
|
const ()
|
|
|
|
// DeriveArgonKey generates a 32-bit cryptographic key using the Argon2id algorithm.
|
|
// Parameters:
|
|
// - password: The plaintext password to be hashed.
|
|
// - salt: The salt as a base64 encoded string.
|
|
// - memLimit: The memory limit in bytes.
|
|
// - opsLimit: The number of iterations.
|
|
//
|
|
// Returns:
|
|
// - A byte slice representing the derived key.
|
|
// - An error object, which is nil if no error occurs.
|
|
func DeriveArgonKey(password, salt string, memLimit, opsLimit int) ([]byte, error) {
|
|
if memLimit < 1024 || opsLimit < 1 {
|
|
return nil, fmt.Errorf("invalid memory or operation limits")
|
|
}
|
|
|
|
// Decode salt from base64
|
|
saltBytes, err := base64.StdEncoding.DecodeString(salt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid salt: %v", err)
|
|
}
|
|
|
|
// Generate key using Argon2id
|
|
// Note: We're assuming a fixed key length of 32 bytes and changing the threads
|
|
key := argon2.IDKey([]byte(password), saltBytes, uint32(opsLimit), uint32(memLimit/1024), 1, 32)
|
|
|
|
return key, nil
|
|
}
|
|
|
|
// DeriveLoginKey derives a login key from the given key encryption key.
|
|
// This loginKey act as user provided password during SRP authentication.
|
|
// Parameters: keyEncKey: This is the keyEncryptionKey that is derived from the user's password.
|
|
func DeriveLoginKey(keyEncKey []byte) []byte {
|
|
subKey, _ := deriveSubKey(keyEncKey, loginSubKeyContext, loginSubKeyId, loginSubKeyLen)
|
|
// return the first 16 bytes of the derived key
|
|
return subKey[:16]
|
|
}
|
|
|
|
func deriveSubKey(masterKey []byte, context string, subKeyID uint64, subKeyLength uint32) ([]byte, error) {
|
|
if subKeyLength < cryptoKDFBlake2bBytesMin || subKeyLength > cryptoKDFBlake2bBytesMax {
|
|
return nil, fmt.Errorf("subKeyLength out of bounds")
|
|
}
|
|
// Pad the context
|
|
ctxPadded := make([]byte, cryptoGenerichashBlake2bPersonalBytes)
|
|
copy(ctxPadded, []byte(context))
|
|
// Convert subKeyID to byte slice and pad
|
|
salt := make([]byte, cryptoGenerichashBlake2bSaltBytes)
|
|
binary.LittleEndian.PutUint64(salt, subKeyID)
|
|
|
|
// Create a BLAKE2b configuration
|
|
config := &blake2b.Config{
|
|
Size: uint8(subKeyLength),
|
|
Key: masterKey,
|
|
Salt: salt,
|
|
Person: ctxPadded,
|
|
}
|
|
hasher, err := blake2b.New(config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hasher.Write(nil) // No data, just using key, salt, and personalization
|
|
return hasher.Sum(nil), nil
|
|
}
|
|
|
|
func DecryptChaChaBase64(data string, key []byte, nonce string) (string, []byte, error) {
|
|
// Decode data from base64
|
|
dataBytes, err := base64.StdEncoding.DecodeString(data)
|
|
if err != nil {
|
|
// safe to log the encrypted data
|
|
return "", nil, fmt.Errorf("invalid base64 data %s: %v", data, err)
|
|
}
|
|
// Decode nonce from base64
|
|
nonceBytes, err := base64.StdEncoding.DecodeString(nonce)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("invalid nonce: %v", err)
|
|
}
|
|
// Decrypt data
|
|
decryptedData, err := decryptChaCha20poly1305(dataBytes, key, nonceBytes)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("failed to decrypt data: %v", err)
|
|
}
|
|
return base64.StdEncoding.EncodeToString(decryptedData), decryptedData, nil
|
|
}
|