Skip to content

Instantly share code, notes, and snippets.

@Dav1dde
Created October 16, 2012 16:52
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Dav1dde/3900517 to your computer and use it in GitHub Desktop.
Save Dav1dde/3900517 to your computer and use it in GitHub Desktop.

Minecraft Encryption with OpenSSL

Definitions

pk = Public Key, sent with the EncryptionKeyRequest

vt = Verify Token, sent unecrypted with the EncryptionKeyRequest

ss = Shared Secret, generated by the client, 16 random bytes

ess = encrypted Shared Secret, encrypted with the servers pk

EncryptionKeyRequest, 0xfd

Packet:

[server_id : string,
 short : pk_length,
 byte[] : pk,
 short : vt_length,
 byte[] : vt
]

The server expects an EncryptionKeyResponse as answer.

To encrypt the generated ss you need to "decode" the sent pk somehow, this is done with the d2i_RSA_PUBKEY function:

#include <openssl/rsa.h>
#include <openssl/x509.h>

unsigned char* pk;
int pk_length;
// pk = packet.public_key;
// pk_length = packet.public_key_length;
RSA* rsa = d2i_RSA_PUBKEY(NULL, pk, pk_length);

An Implementation in D:

import deimos.openssl.rsa;
import deimos.openssl.x509;

RSA* decode_public(const(ubyte)[] public_key) {
    auto mem_ptr = public_key.ptr;
    return d2i_RSA_PUBKEY(null, &mem_ptr, public_key.length);
}

auto rsa = decode_public(packet.public_key[0..packet.public_key_length]);

Next step, encrypting the ss:

int ss_length = 16;
unsigned char* ss = get_random(ss_length);
unsigned char* out_buf = (unsigned char*)malloc(RSA_size(rsa))
unsigned char* ess = RSA_public_encrypt(ss_length, ss, out_buf, RSA_PKCS1_PADDING);

In D:

ubyte[] encrypt(RSA* rsa, ubyte[] data) {
    size_t s = RSA_size(rsa);
    ubyte[] buf = new ubyte[s];
    RSA_public_encrypt(cast(uint)data.length, data.ptr, buf.ptr, rsa, RSA_PKCS1_PADDING);
    return buf;
}

ubyte[] ss = get_random(16);
auto ess = rsa.encrypt(ss); // UFCS = encrypt(rsa, ss);

When you're done, free the RSA Object with RSA_free:

RSA_free(rsa);

EncryptionKeyResponse, 0xfc (S→C)

After this packet the AES (AES 128 CFB8 to be precise) encryption starts, you need the ss (from above) to initialize the AES encryption.

How to initialize the AES context in D (the function names are the same in C):

ubyte[] key = ss;
ubyte[] iv = ss;

EVP_CIPHER_CTX ctx_encrypt;
EVP_CIPHER_CTX ctx_decrypt;

EVP_CIPHER_CTX_init(&ctx_encrypt);
EVP_EncryptInit_ex(&ctx_encrypt, EVP_aes_128_cfb8(), engine, key.ptr, iv.ptr);
       
EVP_CIPHER_CTX_init(&ctx_decrypt);
EVP_DecryptInit_ex(&ctx_decrypt, EVP_aes_128_cfb8(), engine, key.ptr, iv.ptr);

uint _block_size = EVP_CIPHER_block_size(EVP_aes_128_cfb8());

ubyte[] encrypt(T)(T data) if(isArray!T) {
    return encrypt(data.ptr, (ElementEncodingType!T).sizeof*data.length);
}

ubyte[] encrypt(T)(T data, size_t size) if(isPointer!T) {
    ubyte[] out_ = new ubyte[size + _block_size-1];
    int outlen;
    if(!EVP_EncryptUpdate(&ctx_encrypt, out_.ptr, &outlen, cast(const(ubyte)*)data, cast(int)size)) {
        throw new OpenSSLException();
    }
    out_.length = outlen;

    return out_;
}

ubyte[] decrypt(T)(T data) if(isArray!T) {
    return decrypt(data.ptr, (ElementEncodingType!T).sizeof*data.length);
}

ubyte[] decrypt(T)(T data, size_t size) if(isPointer!T) {
    ubyte[] out_ = new ubyte[size + _block_size];
    int outlen;
    if(!EVP_DecryptUpdate(&ctx_decrypt, out_.ptr, &outlen, cast(const(ubyte)*)data, cast(int)size)) {
        throw new OpenSSLException();
    }
    out_.length = outlen;

    return out_;
}
@martijn-heil
Copy link

martijn-heil commented Nov 1, 2016

EncryptionKeyResponse, 0xfc is not bound to the client as you indicate with EncryptionKeyResponse, 0xfc (S→C),
it is bound to the server. So that would be EncryptionKeyResponse, 0xfc (C→S)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment