mautrix-go/crypto/utils/utils.go

133 lines
3.7 KiB
Go

// Copyright (c) 2020 Nikos Filippakis
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package utils
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"strings"
"go.mau.fi/util/base58"
"golang.org/x/crypto/hkdf"
"golang.org/x/crypto/pbkdf2"
)
const (
// AESCTRKeyLength is the length of the AES256-CTR key used.
AESCTRKeyLength = 32
// AESCTRIVLength is the length of the AES256-CTR IV used.
AESCTRIVLength = 16
// HMACKeyLength is the length of the HMAC key used.
HMACKeyLength = 32
// SHAHashLength is the length of the SHA hash used.
SHAHashLength = 32
)
// XorA256CTR encrypts the input with the keystream generated by the AES256-CTR algorithm with the given arguments.
func XorA256CTR(source []byte, key [AESCTRKeyLength]byte, iv [AESCTRIVLength]byte) []byte {
block, _ := aes.NewCipher(key[:])
cipher.NewCTR(block, iv[:]).XORKeyStream(source, source)
return source
}
// GenAttachmentA256CTR generates a new random AES256-CTR key and IV suitable for encrypting attachments.
func GenAttachmentA256CTR() (key [AESCTRKeyLength]byte, iv [AESCTRIVLength]byte) {
_, err := rand.Read(key[:])
if err != nil {
panic(err)
}
// The last 8 bytes of the IV act as the counter in AES-CTR, which means they're left empty here
_, err = rand.Read(iv[:8])
if err != nil {
panic(err)
}
return
}
// GenA256CTRIV generates a random IV for AES256-CTR with the last bit set to zero.
func GenA256CTRIV() (iv [AESCTRIVLength]byte) {
_, err := rand.Read(iv[:])
if err != nil {
panic(err)
}
iv[8] &= 0x7F
return
}
// DeriveKeysSHA256 derives an AES and a HMAC key from the given recovery key.
func DeriveKeysSHA256(key []byte, name string) ([AESCTRKeyLength]byte, [HMACKeyLength]byte) {
var zeroBytes [32]byte
derivedHkdf := hkdf.New(sha256.New, key[:], zeroBytes[:], []byte(name))
var aesKey [AESCTRKeyLength]byte
var hmacKey [HMACKeyLength]byte
derivedHkdf.Read(aesKey[:])
derivedHkdf.Read(hmacKey[:])
return aesKey, hmacKey
}
// PBKDF2SHA512 generates a key of the given bit-length using the given passphrase, salt and iteration count.
func PBKDF2SHA512(password []byte, salt []byte, iters int, keyLenBits int) []byte {
return pbkdf2.Key(password, salt, iters, keyLenBits/8, sha512.New)
}
// DecodeBase58RecoveryKey recovers the secret storage from a recovery key.
func DecodeBase58RecoveryKey(recoveryKey string) []byte {
noSpaces := strings.ReplaceAll(recoveryKey, " ", "")
decoded := base58.Decode(noSpaces)
if len(decoded) != AESCTRKeyLength+3 { // AESCTRKeyLength bytes key and 3 bytes prefix / parity
return nil
}
var parity byte
for _, b := range decoded[:34] {
parity ^= b
}
if parity != decoded[34] || decoded[0] != 0x8B || decoded[1] != 1 {
return nil
}
return decoded[2:34]
}
// EncodeBase58RecoveryKey recovers the secret storage from a recovery key.
func EncodeBase58RecoveryKey(key []byte) string {
var inputBytes [35]byte
copy(inputBytes[2:34], key[:])
inputBytes[0] = 0x8B
inputBytes[1] = 1
var parity byte
for _, b := range inputBytes[:34] {
parity ^= b
}
inputBytes[34] = parity
recoveryKey := base58.Encode(inputBytes[:])
var spacedKey string
for i, c := range recoveryKey {
if i > 0 && i%4 == 0 {
spacedKey += " "
}
spacedKey += string(c)
}
return spacedKey
}
// HMACSHA256B64 calculates the unpadded base64 of the SHA256 hmac of the input with the given key.
func HMACSHA256B64(input []byte, hmacKey [HMACKeyLength]byte) string {
h := hmac.New(sha256.New, hmacKey[:])
h.Write(input)
return base64.RawStdEncoding.EncodeToString(h.Sum(nil))
}