Difference between revisions of "Protocol Encryption"
(→Server: clarify it's a HTTP GET, not a POST) |
|||
Line 82: | Line 82: | ||
=== Server === | === Server === | ||
− | After decrypting the shared secret in the second Encryption Response, the server generates the login hash as above and sends | + | After decrypting the shared secret in the second Encryption Response, the server generates the login hash as above and sends a HTTP GET to |
<nowiki>https://sessionserver.mojang.com/session/minecraft/hasJoined?username=</nowiki>''username''&serverId=''hash'' | <nowiki>https://sessionserver.mojang.com/session/minecraft/hasJoined?username=</nowiki>''username''&serverId=''hash'' | ||
(Case sensitive username, must match the client's username!) | (Case sensitive username, must match the client's username!) |
Revision as of 09:06, 14 August 2016
As of 12w17a, Minecraft implements SSL-like encryption.
Contents
Overview
C->S : Handshake State=2 C->S : Login Start S->C : Encryption Key Request (Client Auth) C->S : Encryption Key Response (Server Auth, Both enable encryption) S->C : Login Success
- see Protocol FAQ to get information about what happens next.
Server ID String
Update (1.7.x): The server ID is now sent as an empty string. Hashes also utilize the public key, so they will still be correct.
Pre-1.7.x: The server ID string is a randomly-generated string of characters with a maximum length of 20 code points (the client disconnects with an exception if the length is longer than 20).
The client appears to arrive at incorrect hashes if the server ID string contains certain unprintable characters, so for consistent results only characters with code points in the range U+0021-U+007E (inclusive) should be sent. This range corresponds to all of ASCII with the exception of the space character (U+0020) and all control characters (U+0000-U+001F, U+007F).
The client appears to arrive at incorrect hashes if the server ID string is too short. 15 to 20 (inclusive) length strings have been observed from the Notchian server and confirmed to work as of 1.5.2.
Key Exchange
The server generates a 1024-bit RSA keypair on startup. The key, when packed into a Encryption Request packet, is in ASN.1 format as defined by x.509. The ASN.1 structure looks as follows
SubjectPublicKeyInfo ::= SEQUENCE { algorithm SEQUENCE { algorithm OBJECT IDENTIFIER parameters ANY OPTIONAL } subjectPublicKey BITSTRING } SubjectPublicKey ::= SEQUENCE { modulus INTEGER publicExponent INTEGER }
If you're struggling to import this using a crypto library, you can convert it to common PEM by base64-encoding and wrapping in '-----BEGIN PUBLIC KEY-----' and '-----END PUBLIC KEY-----'.
Symmetric Encryption
On receipt of a Encryption Request from the server, the client will generate a 16-byte shared secret, to be used with the AES/CFB8 stream cipher. It then encrypts it with the server's public key (PKCS#1 v1.5 padded), and also encrypts the verify token received in the Encryption Request packet in the same way, then sends both to the server in a Encryption Response packet. Note that both byte arrays in the Encryption Response packet will be 128 bytes long because of the padding.
The server decrypts the shared secret and token using its private key, and checks if the token is the same. It then sends a Login Success, and enables AES/CFB8 encryption. For the Initial Vector (IV) and AES setup, both sides use the shared secret key. Similarly, the client will also enable encryption upon sending Encryption Response. From this point forward, everything is encrypted.
The Login Success packet is sent encrypted.
Authentication
Both server and client need to make a request to sessionserver.mojang.com if the server is in online-mode.
Client
After generating the shared secret, the client generates the following hash:
sha1 := Sha1() sha1.update(ASCII encoding of the server id string from Encryption Request) sha1.update(shared secret) sha1.update(server's encoded public key from Encryption Request) hash := sha1.hexdigest() # String of hex characters
Note that the Sha1.hexdigest() method used by minecraft removes leading zeros and uses the two's-complement of negative numbers prefixed with a minus sign:
sha1(Notch) : 4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48 sha1(jeb_) : -7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1 sha1(simon) : 88e16a1019277b15d58faf0541e11910eb756f6
The resulting hash is then sent via an HTTP POST request to
https://sessionserver.mojang.com/session/minecraft/join
With the following sent as post data, Content-Type: application/json
{
"accessToken": "<accessToken>",
"selectedProfile": "<selectedProfile>",
"serverId": "<serverHash>"
}
Server
After decrypting the shared secret in the second Encryption Response, the server generates the login hash as above and sends a HTTP GET to
https://sessionserver.mojang.com/session/minecraft/hasJoined?username=username&serverId=hash
(Case sensitive username, must match the client's username!)
The response is a JSON object containing the user's UUID and skin blob
{
"id": "<profile identifier>",
"name": "<player name>",
"properties": [
{
"name": "textures",
"value": "<base64 string>",
"signature": "<base64 string; signed data using Yggdrasil's private key>"
}
]
}
Sample Code
Examples of generating Minecraft-style hex digests:
- C#: http://git.io/kO6Ejg
- node.js: http://git.io/v2ue_A
- Go: http://git.io/-5ORag
- Java: http://git.io/vzbmS
Additional Links
Encrypt shared secret using OpenSSL
Generate RSA-Keys and building the ASN.1v8 structure of the x.509 certificate using Crypto++
Decrypt shared secret using Crypto++