-
Notifications
You must be signed in to change notification settings - Fork 3
/
wacrypt.class.php
215 lines (180 loc) · 5.39 KB
/
wacrypt.class.php
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
<?php
abstract class WACrypt{
/**
* @var string Protocol version header
*/
public $protocolVersion = "\x57\x41\x02\x00"; // WA20
/**
* Noise_XX_25519_AESGCM_SHA256 in this case
* @var string some string using during Noise handshake.
*/
public $handshakeName = "\x4E\x6F\x69\x73\x65\x5F\x58\x58\x5F\x32\x35\x35\x31\x39\x5F\x41\x45\x53\x47\x43\x4D\x5F\x53\x48\x41\x32\x35\x36\x00\x00\x00\x00";
/**
* @var HandshakeHash chain hash
*/
public $handshakeChain;
/**
* @var string AES key
*/
public $handshakeAES;
/**
* @var string nonce
*/
public $handshakeNonce;
/**
* @var string write key
*/
public $writeKey;
/**
* @var string read key
*/
public $readKey;
/**
* @var string write nonce
*/
public $writeNonce;
/**
* @var string read nonce
*/
public $readNonce;
/**
* @var string curve private key
*/
public $curvePrivate;
/**
* @var string curve public key
*/
public $curvePublic;
/**
* @var string chain key
*/
public $chainKey;
/**
* @var string ephemeral private key
*/
public $ephemeralPrivateKey;
/**
* @var string ephemeral public key
*/
public $ephemeralPublicKey;
/**
* @var string shared public key
*/
public $sharedPublicKey;
/**
* @var string shared curve key
*/
public $sharedCurveKey;
/**
* @var string chat static public key & cert, used during Noise handshake
*/
public $chatStaticCert;
/**
* Encrypt stream
* @param string $payload binary data
* @param bool $compress whether to compress data
* @return string encrypted data
*/
public function encryptStream($payload, $compress = false){
$iv = pack('NNN', 0, 0, $this->writeNonce);
$flags = 0x00;
if($compress){
$flags |= 0x02;
$payload = zlib_encode($payload, ZLIB_ENCODING_DEFLATE, 1);
}
$payload = pack('C', $flags) . $payload;
$cipher = Crypto\Cipher::aes(Crypto\Cipher::MODE_GCM, 256);
$result = $cipher->encrypt($payload, $this->writeKey, $iv);
$result .= $cipher->getTag();
$this->writeNonce++;
return $result;
}
/**
* Decrypt stream
* @param string $payload binary data
* @return string decrypted data
*/
public function decryptStream($payload){
$iv = pack('NNN', 0, 0, $this->readNonce);
$data = substr($payload, 0, -16);
$tag = substr($payload, -16);
$cipher = Crypto\Cipher::aes(Crypto\Cipher::MODE_GCM, 256);
$cipher->setTag($tag);
$result = $cipher->decrypt($data, $this->readKey, $iv);
$this->readNonce++;
$flags = ord($result[0]);
$result = substr($result, 1);
if($flags & 0x02 === 0x02){
$result = zlib_decode($result);
}
return $result;
}
/**
* Encrypt handshake
* @param string $payload binary data
* @return string encrypted data
*/
public function handshakeEncrypt($payload){
switch(true){
case $this instanceof Client:
$iv = pack('NNN', 0, 0, $this->handshakeNonce);
break;
case $this instanceof Server:
$iv = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
break;
default:
throw new RuntimeException('Incorrect usage');
break;
}
$cipher = Crypto\Cipher::aes(Crypto\Cipher::MODE_GCM, 256);
$cipher->setAAD($this->handshakeChain->getHash());
$result = $cipher->encrypt($payload, $this->handshakeAES, $iv);
$result .= $cipher->getTag();
$this->handshakeChain->update($result);
if($this instanceof Server){
$this->handshakeNonce++;
}
return $result;
}
/**
* Decrypt handshake
* @param string $payload binary data
* @return mixed decrypted data
*/
public function handshakeDecrypt($payload){
switch(true){
case $this instanceof Client:
$iv = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
break;
case $this instanceof Server:
$iv = pack('NNN', 0, 0, $this->handshakeNonce);
break;
default:
throw new RuntimeException('Incorrect usage');
break;
}
$data = substr($payload, 0, -16);
$tag = substr($payload, -16);
$cipher = Crypto\Cipher::aes(Crypto\Cipher::MODE_GCM, 256);
$cipher->setTag($tag);
$cipher->setAAD($this->handshakeChain->getHash());
$result = $cipher->decrypt($data, $this->handshakeAES, $iv);
$this->handshakeChain->update($payload);
if($this instanceof Client){
$this->handshakeNonce++;
}
return $result;
}
/**
* Handshake set shared key
* @param string $private private key
* @param string $public public key
*/
public function handshakeSetKey($private, $public){
$agreement = curve25519_shared($private, $public);
$hkdf = hkdf($agreement, 'sha256', $this->chainKey, 64);
$this->chainKey = substr($hkdf, 0, 32);
$this->handshakeAES = substr($hkdf, 32, 32);
$this->handshakeNonce = 0;
}
}