summary refs log tree commit diff
path: root/vendor/maunium.net/go/mautrix/crypto/utils/utils.go
blob: e2f8a19c3c4a92af552bc8d9fa04d8639cf651e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// 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))
}