-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathblock.go
More file actions
285 lines (251 loc) · 7.66 KB
/
block.go
File metadata and controls
285 lines (251 loc) · 7.66 KB
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"encoding/gob"
"errors"
"fmt"
"time"
"golang.org/x/crypto/sha3"
)
type BlockData struct {
EncryptedKey string // RSA encrypted AES key
EncryptedMessage string // AES encrypted message
Salt [8]byte // Random salt
Parent [64]byte // Hash of parent block
Nonce []byte // Nonce used for GCM
}
type Block struct {
// Block Data
Data BlockData
// Non-hashed data
ID [64]byte // Hash of block data
Pepper [8]byte // Random non-hashed salt
}
// Returns n random bytes
func RandomBytes(n int) []byte {
out := make([]byte, n)
_, err := rand.Read(out)
if err != nil {
panic(err)
}
return out
}
// Selects a block parent based on the encrypted message
func selectParentHash(encryptedMessage string) [64]byte {
out := [64]byte{}
copy(out[:], ([]byte(SelectParentHash(encryptedMessage)))[:64])
return out
}
func CreateBlockData(message string, key *rsa.PublicKey) BlockData {
var out BlockData
// Controls how long we wait for encryption to complete
// Go doesn't perform encryptions in constant-time...
// So to prevent timing attacks, we wait after encryption
// The time is taken before running the encryption, and then after encrypt
// we wait until that much time has elapsed
// Thus, we get pseudo-constant time behavior
// This time needs to be long enough that encryption of the key and of the
// message will be complete, each in one period, for any (reasonable) message.
constantDelayFactor := 250 * time.Millisecond
// Block salt
copy(out.Salt[:], RandomBytes(8)[:8])
// Message encryption
// Random AES256 key
fmt.Println("Generating AES256 key...")
AESkey := RandomBytes(32)
// Block cipher for that key
AESCipher, e := aes.NewCipher(AESkey)
if e != nil {
panic(e)
}
fmt.Println("Done.\n")
fmt.Println("")
// AEAD
fmt.Println("Applying AES256-GCM to message text...")
auth, err := cipher.NewGCM(AESCipher)
if err != nil {
panic(err)
}
// Initialization Vector
// Delivered with ciphertext as it is necessary for decryption...
// But it doesn't have to be private to be secure
out.Nonce = RandomBytes(auth.NonceSize())
// Plaintext bytes
plaintext := []byte(message)
// Encryption
// First, get current time and add delay factor
endpoint := time.Now().Add(constantDelayFactor)
// Then actually run encryption
cipherBytes := auth.Seal(nil, out.Nonce, plaintext, out.Salt[:])
// Now, delay until we reach endpoint
time.Sleep(time.Until(endpoint))
// Convert to base64 and place in block
out.EncryptedMessage = base64.URLEncoding.EncodeToString(cipherBytes)
fmt.Println("Done.\n")
fmt.Println("")
// Select blockparent using blockpool
fmt.Println("Selecting block parent...")
out.Parent = selectParentHash(out.EncryptedMessage)
fmt.Println("Done.\n")
fmt.Println("")
// AES key encryption
// First, get current time and add delay factor
fmt.Println("Encrypting AES key with RSA...")
endpoint = time.Now().Add(constantDelayFactor)
// Then actually run encryption
cipheredKey, e := rsa.EncryptOAEP(sha3.New512(), rand.Reader, key, AESkey, out.Parent[:])
// Now, delay until we reach endpoint
time.Sleep(time.Until(endpoint))
// Panic on error
if e != nil {
panic(e)
}
// Convert to base64 and place in block
out.EncryptedKey = base64.URLEncoding.EncodeToString(cipheredKey)
// Done.
fmt.Println("Done.\n")
fmt.Println("")
return out
}
// BlockData -> string
func StringifyBlockData(data BlockData) string {
var buf bytes.Buffer
encoder := gob.NewEncoder(&buf)
err := encoder.Encode(data)
if err != nil {
panic(err)
}
return buf.String()
}
func DestringifyBlockData(data string) BlockData {
var out BlockData
var buf bytes.Buffer
buf.WriteString(data)
decoder := gob.NewDecoder(&buf)
err := decoder.Decode(&out)
if err != nil {
panic(err)
}
return out
}
func CreateBlock(message string, key *rsa.PublicKey) Block {
var out Block
// Block data
// This is where encryption is done...
// Constant factor delay?
out.Data = CreateBlockData(message, key)
// Block ID
dataString := StringifyBlockData(out.Data)
hasher := sha3.New512()
_, err := hasher.Write([]byte(dataString))
if err != nil {
panic(err)
}
copy(out.ID[:], hasher.Sum(nil)[:64])
// Block pepper
copy(out.Pepper[:], RandomBytes(8)[:8])
return out
}
func StringifyBlock(block Block) string {
var buf bytes.Buffer
encoder := gob.NewEncoder(&buf)
err := encoder.Encode(block)
if err != nil {
panic(err)
}
return buf.String()
}
func DestringifyBlock(block string) Block {
var out Block
var buf bytes.Buffer
buf.WriteString(block)
decoder := gob.NewDecoder(&buf)
err := decoder.Decode(&out)
if err != nil {
panic(err)
}
return out
}
// Returns the decrypted message from a block with a given PrivateKey
func AttemptDecrypt(block Block, key *rsa.PrivateKey) (message string, err error) {
// First off, we confirm the integrity of the block data
// If the blockID doesn't match the hash of the blockdata, then it has been modified
// If that occurs, report an error
fmt.Println("Verifying hashes...")
dataString := StringifyBlockData(block.Data)
hasher := sha3.New512()
if _, err := hasher.Write([]byte(dataString)); err != nil {
return "", err
}
test := hasher.Sum(nil)
if !bytes.Equal(test, block.ID[:]) {
// The blockdata has been modified!
// Error out
return "", errors.New("Blockdata hash mismatch: ID " + string(block.ID[:64]) +
" is not equal to hash of data " + string(test[:64]))
}
fmt.Println("Hash of data matches set ID.\n")
fmt.Println("")
// No data tampering has occurred if we get here...
// Or if it has, it caused a collision in SHA3-512, which is insanely unlikely
// Controls how long we wait for decryption to complete
// Go doesn't perform encryptions in constant-time...
// So to prevent timing attacks, we wait after decryption
// The time is taken before running the decryption, and then after encrypt
// we wait until that much time has elapsed
// Thus, we get pseudo-constant time behavior
// This time needs to be long enough that decryption of the key and of the
// message will be complete, each in one period, for any (reasonable) message.
constantDelayFactor := 250 * time.Millisecond
// First, attempt to decrypt the encryptedKey
// First, get our encrypted key as a byte array
fmt.Println("Performing RSA decryption of encrypted AES key...")
encryptedKeyBytes, er := base64.URLEncoding.DecodeString(block.Data.EncryptedKey)
if er != nil {
return "", er
}
// Then, get current time and add constant delay factor
endpoint := time.Now().Add(constantDelayFactor)
// Then actually attempt decryption
AESkey, e := rsa.DecryptOAEP(sha3.New512(), rand.Reader, key, encryptedKeyBytes, block.Data.Parent[:])
// Now wait until endpoint
time.Sleep(time.Until(endpoint))
// Return on error
if e != nil {
return "", e
}
fmt.Println("Done.\n")
fmt.Println("")
// Now, attempt to use that key to decrypt the encryptedMessage
fmt.Println("Performing AES256-GCM decryption of message ciphertext...")
AESCipher, err := aes.NewCipher(AESkey)
if err != nil {
return "", err
}
msg, error := base64.URLEncoding.DecodeString(block.Data.EncryptedMessage)
if error != nil {
return "", error
}
auth, er := cipher.NewGCM(AESCipher)
if er != nil {
return "", er
}
// First, get current time and add constant delay factor
endpoint = time.Now().Add(constantDelayFactor)
// Now actually attempt decryption
msg, error = auth.Open(nil, block.Data.Nonce, msg, block.Data.Salt[:])
// Now wait until endpoint
time.Sleep(time.Until(endpoint))
if error != nil {
return "", error
}
fmt.Println("Done.\n")
fmt.Println("")
// And return
return string(msg), nil
}