Bonjour tout le monde,
La partie authentification, et plus précisément, la partie permettant de générer le credentials a envoyer lors de l'IdentificationMessage étant une partie que beaucoup de développeurs de bot D2 débutant ont du mal assimiler, je me permets de partager ma classe Java qui gère cet aspect.
package com.zyra.dofusbot.business;
import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
/**
* Permet de générer le credentials a envoyer dans l'IdentificationMessage.
*
* @author zyra
*/
public final class GenerateCrendentials {
/**
* Clé publique encodée en base 64 contenu dans les sources du jeu.
*/
private static final String STATIC_PUB_KEY_B64 =
"MIIBUzANBgkqhkiG9w0BAQEFAAOCAUAAMIIBOwKCATIAgucoka9J2PXcNdjcu6CuDmgteIMB+rih"
+ "2UZJIuSoNT/0J/lEKL/W4UYbDA4U/6TDS0dkMhOpDsSCIDpO1gPG6+6JfhADRfIJItyHZflyXNUj"
+ "WOBG4zuxc/L6wldgX24jKo+iCvlDTNUedE553lrfSU23Hwwzt3+doEfgkgAf0l4ZBez5Z/ldp9it"
+ "2NH6/2/7spHm0Hsvt/YPrJ+EK8ly5fdLk9cvB4QIQel9SQ3JE8UQrxOAx2wrivc6P0gXp5Q6bHQo"
+ "ad1aUp81Ox77l5e8KBJXHzYhdeXaM91wnHTZNhuWmFS3snUHRCBpjDBCkZZ+CxPnKMtm2qJIi57R"
+ "slALQVTykEZoAETKWpLBlSm92X/eXY2DdGf+a7vju9EigYbX0aXxQy2Ln2ZBWmUJyZE8B58CAwEA"
+ "AQ==";
/**
* Constructeur privé de la classe utilitaire.
*/
private GenerateCrendentials() {
}
/**
* Permet d'instancier un objet Java correspondant à la clé publique
* contenu dans les sources du jeu.
*
* @return L'objet java contenant la clé publique.
*/
private static PublicKey getStaticPubKey() {
byte[] binaryKey = Base64.getDecoder().decode(STATIC_PUB_KEY_B64.getBytes());
KeySpec spec = new X509EncodedKeySpec(binaryKey);
try {
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
} catch (Exception exception) {
return null;
}
}
/**
* Permet de récupérer la clé publique contenu dans le HelloConnectMessage.
*
* @param pStaticPubKey La clé publique contenu dans les sources du jeu.
* @param pDatas La clé publique cryptée par le serveur qui est contenu dans
* le HelloConnectMessage.
* @return L'objet java contenant la clé publique contenu dans le HelloConnectMessage.
*/
private static PublicKey decryptPubKey(final PublicKey pStaticPubKey,
final byte[] pDatas) {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, pStaticPubKey);
byte[] binaryDatas = cipher.doFinal(pDatas);
KeySpec spec = new X509EncodedKeySpec(binaryDatas);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
} catch (Exception exception) {
return null;
}
}
/**
* Permet de générer le credentials qui devra être envoyé dans l'IdentificationMessage.
*
* @param pPubKey La clé publique contenu dans le HelloConnectMessage
* (décrypté par la méthode decryptPubKey)
* @param pAesKey Une clé AES généré aléatoirement qui doit avoir une taille de 32 octets.
* @param pSalt Le salt reçus dans le HelloConnectMessage qui doit avoir une taille de 32 octets.
* @param pUser Le nom d'utilisateur pour se connecter au jeu.
* @param pPassword Le mot de passe de l'utilisateur.
* @return Le credentials a envoyer dans l'IdentificationMessage.
*/
private static byte[] encryptCredentials(final PublicKey pPubKey, final byte[] pAesKey,
final String pSalt, final String pUser, final String pPassword) {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pPubKey);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
CipherOutputStream stream = new CipherOutputStream(baos, cipher);
/**
* Les données doivent être crypter avec la clé publique reçus dans le HelloConnectMessage.
* Elles doivent avoir le format suivant :
*
* 32 octets : Salt
* 32 octets : Clé AES
* 1 octet : La taille du nom d'utilisateur
* Taille variable (inférieur à 255 octets) : Le nom de l'utilisateur
* Taille variable : Le mot de passe du compte
*/
stream.write(pSalt.getBytes());
stream.write(pAesKey);
stream.write((byte) pUser.length());
stream.write(pUser.getBytes());
stream.write(pPassword.getBytes());
stream.close();
return baos.toByteArray();
} catch (Exception exception) {
return null;
}
}
/**
* Permet d'obtenir le credentials qui devra être envoyé dans l'IdentificationMessage.
*
* @param pEncodedKey La clé publique cryptée par le serveur reçus dans le HelloConnectMessage.
* (données brut)
* @param pSalt Le salt reçus dans le HelloConnectMessage qui avoir une taille de 32 octets.
* @param pAesKey Une clé AES générée aléatoirement qui doit avoir une taille de 32 octets.
* @param pUser Le nom d'utilisateur pour se connecter au jeu.
* @param pPassword Le mot de passe de l'utilisateur.
* @return Le credentials a envoyer dans l'IdentificationMessage.
*/
public static byte[] getCredentials(final byte[] pEncodedKey, final String pSalt,
final byte[] pAesKey, final String pUser, final String pPassword) {
PublicKey staticPubKey = getStaticPubKey();
PublicKey pubKey = decryptPubKey(staticPubKey, pEncodedKey);
return encryptCredentials(pubKey, pAesKey, pSalt, pUser, pPassword);
}
}
La méthode a utiliser pour générer le credentials est l'unique méthode publique de la classe : getCredentials
Elle prend les arguments suivant :
- pEncodedKey : Les données brut extraites du HelloConnectMessage qui contiennent la clé publique cryptée.
- pSalt : La champ salt reçus dans le HelloConnectMessage.
- pAesKey : Une clé AES de 32 octets qui est générée aléatoirement.
- pUser : Le nom de compte du jeu.
- pPassword : Le mot de passe de votre compte.
La méthode retourne les données brut du credentials qui devra être envoyé dans l'IdentificationMessage (Ces données étant dans un tableau de byte).
Pour les personnes qui ne savent pas comment récupérer les informations du HelloConnectMessage, je vous recommande la lecture du tutoriel adapté : https://cadernis.com/d/115-comprendre-le-protocole-de-dofus