After RA2ne security type is selected server sends its RSA public key of 2048 bits. Note that the modulus and the public exponent are big-endian big integers.
No. of bytes | Type | Value | Description |
---|---|---|---|
4 | U32 | 2048 | Key length in bits |
256 | U8 array | Modulus (n) | |
256 | U8 array | Public exponent (e) |
Client also sends its public key.
No. of bytes | Type | Value | Description |
---|---|---|---|
4 | U32 | 2048 | Key length in bits |
256 | U8 array | Modulus (n) | |
256 | U8 array | Public exponent (e) |
Server generates a random number of 16 bytes and encrypts it with the client's public key. EME-PKCS1-v1_5 padding algorithm is used. Then server sends the encrypted random number.
No. of bytes | Type | Value | Description |
---|---|---|---|
2 | U16 | 256 | Length of encrypted message |
256 | U8 array | Encrypted random number |
Client also generates a random number of 16 bytes, encrypts it with the server's public key and then sends it.
No. of bytes | Type | Value | Description |
---|---|---|---|
2 | U16 | 256 | Length of encrypted message |
256 | U8 array | Encrypted random number |
Server's random number is decrypted with the client's private key. At the same time, client's random number is decrypted with the server's private key. Now both random numbers are known by both sides. Client session key and server session key can be derived as follows.
ClientSessionKey = the first 16 bytes of SHA1(ServerRandom || ClientRandom)
ServerSessionKey = the first 16 bytes of SHA1(ClientRandom || ServerRandom)
("||" means concatenation)
ClientSessionKey is used to encrypted the message from client to server. ServerSessionKey is used to encrypted the message from server to client.
After that encrypted messages will be encrypted with AES-CTR and will be sent followed by a message authentication code. Every time a message is encrypted, a new IV is generated from the session key and the value of a 16-byte counter of the messages. Then the encrypted message is used to generate a MAC. The new value of the counter is computed for the encryption of the next time. When client or server receives an encrypted message, it's supposed to authenticate the message. If the authentication fails, the connection should be closed. Reference implementation of the algorithm is as follows.
class RA2Cipher {
constructor(key) {
// PRNG is used to generate IV for every encrypted message.
// session key is also used as PRNG's seed.
this._key = key;
this._aes = new aesjs.AES(this._key);
this._counter = new Uint8Array(16);
this._iv = null;
// a and b are parameters for PRNG.
this._a = this._aes.encrypt(new Uint8Array(16));
this._b = new Uint8Array(16);
const v = this._a[0] >> 6;
for (let i = 0; i < 15; i++) {
this._b[i] = (this._a[i + 1] >> 6) | (this._a[i] << 2);
this._a[i] = (this._a[i + 1] >> 7) | (this._a[i] << 1);
}
this._b[14] ^= v >> 1;
const magic_numbers = new Uint8Array([0x0, 0x87, 0x0e, 0x89]);
this._b[15] = 4 * this._a[15] ^ magic_numbers[v];
this._a[15] = 2 * this._a[15] ^ magic_numbers[v >> 1];
}
encrypt(plaintext_msg) {
this._compute_iv();
let ctr = new aesjs.ModeOfOperation.ctr(this._key, new aesjs.Counter(this._iv));
const encrypted = ctr.encrypt(plaintext_msg);
const mac = this._mac(encrypted);
let msg = new Uint8Array(encrypted.length + mac.length);
msg.set(encrypted);
msg.set(mac, encrypted.length);
for (let i = 0; i < 16 && this._counter[i]++ == 255; i++);
return msg;
}
decrypt(msg) {
this._compute_iv();
const encrypted = msg.subarray(0, msg.length - 16);
const mac = this._mac(encrypted);
for (let i = 0; i < 16; i++) {
if (mac[i] != msg[msg.length - 16 + i]) {
return undefined; // failed to authenticate the message
}
}
let ctr = new aesjs.ModeOfOperation.ctr(this._key, new aesjs.Counter(this._iv));
const decrypted = ctr.decrypt(encrypted);
for (let i = 0; i < 16 && this._counter[i]++ == 255; i++);
return decrypted;
}
_compute_iv() {
this._iv = this._aes.encrypt(new Uint8Array(16));
for (let i = 0; i < 16; i++) {
this._iv[i] ^= this._counter[i] ^ this._a[i];
}
this._iv = this._aes.encrypt(this._iv);
}
_mac(encrypted, iv) {
let c = this._aes.encrypt(new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
]));
c[0] ^= (encrypted.length & 0xff00) >> 8;
c[1] ^= encrypted.length & 0xff;
c[2] ^= 0x80;
for (let i = 0; i < 16; i++) {
c[i] ^= this._b[i];
}
c = this._aes.encrypt(c);
// compute the last block of CFB
let buff = this._aes.encrypt(new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2
]));
for (let i = 0, j = 0; i < encrypted.length; i++) {
if (j == 16) {
buff = this._aes.encrypt(buff);
j = 1;
buff[0] ^= encrypted[i]
} else {
buff[j] ^= encrypted[i];
j++;
}
}
if (encrypted.length % 16 != 0) {
buff[encrypted.length % 16] ^= 0x80;
for (let i = 0; i < 16; i++) {
buff[i] ^= this._b[i];
}
} else {
for (let i = 0; i < 16; i++) {
buff[i] ^= this._a[i];
}
}
buff = this._aes.encrypt(buff);
let out = new Uint8Array(16);
for (let i = 0; i < 16; i++) {
out[i] = this._iv[i] ^ buff[i] ^ c[i];
}
return out;
}
}
Server sends encrypted server hash.
ServerHash = SHA1(ServerPublicKey || ClientPublicKey)
Note that ServerPublicKey and ClientPublicKey are both of 516 bytes
No. of bytes | Type | Value | Description |
---|---|---|---|
2 | U16 | 20 | Length of ServerHash |
20 | U8 array | Encrypted ServerHash | |
16 | U8 array | MAC |
Client sends encrypted client hash.
ClientHash = SHA1(ClientPublicKey || ServerPublicKey)
No. of bytes | Type | Value | Description |
---|---|---|---|
2 | U16 | 20 | Length of ClientHash |
20 | U8 array | Encrypted ClientHash | |
16 | U8 array | MAC |
After decryption, server should compare received ClientHash with the one it computes itself. Vice versa.
Server sends subtype to let client know what credentials are needed.
No. of bytes | Type | Value | Description |
---|---|---|---|
2 | U16 | 1 | Length of RA2Subtype |
1 | U8 array | Encrypted RA2Subtype | |
16 | U8 array | MAC |
RA2Subtype is 1 if both username and password are needed. Or it's 2 if only password is needed. Other values are invalid.
Client sends login credentials.
No. of bytes | Type | Description |
---|---|---|
2 | U16 | Length of crendentials |
Length of crendentials | U8 array | Encrypted crendentials |
16 | U8 array | MAC |
Plaintext message of the credentials is:
No. of bytes | Type | Description |
---|---|---|
1 | U8 | Length of username (is zero if subtype is 2) |
Length of username (can be zero) | U8 array | Username |
1 | U8 | Length of password |
Length of password | U8 array | Password |
After receiving the credentials, the server continues with the SecurityResult message.
RA2 security type is almost the same except that if it is chosen, not only credentials but also all the following messages will be encrypted. Note that a RA2 encrypted message is not necessarily a RFB message, vice versa. That is to say RA2 encryption layer is an independent layer between the TCP layer and the RFB layer.
For RA2_256 (129):
The encryption algorithm is the same, except that key length is 32 bytes.