@Override
 public void handle(NetworkContext context, MessageForgeHandshakeInOutAck message) {
   Session session = context.getSession();
   Attribute<ForgeServerHandshakePhase> phase =
       context.getChannel().attr(ForgeHandshakePhase.PHASE);
   switch (phase.get()) {
     case WAITING_ACK:
       if (!message.getPhase().equals(ForgeClientHandshakePhase.WAITING_SERVER_DATA)) {
         session.disconnect(
             "Retrieved unexpected forge handshake ack message. (Got "
                 + message.getPhase()
                 + ", expected "
                 + ForgeClientHandshakePhase.WAITING_SERVER_DATA
                 + ")");
       } else {
         List<MessageForgeHandshakeOutRegistryData.Entry> entries = Lists.newArrayList();
         entries.add(
             new MessageForgeHandshakeOutRegistryData.Entry(
                 "fml:items", Maps.newHashMap(), Lists.newArrayList()));
         entries.add(
             new MessageForgeHandshakeOutRegistryData.Entry(
                 "fml:blocks", Maps.newHashMap(), Lists.newArrayList()));
         session.send(new MessageForgeHandshakeOutRegistryData(entries));
         session.send(new MessageForgeHandshakeInOutAck(ForgeServerHandshakePhase.WAITING_ACK));
         phase.set(ForgeServerHandshakePhase.COMPLETE);
       }
       LanternGame.log()
           .info(
               "{}: Forge handshake -> Received ack (waitingServerData) message.",
               session.getGameProfile().getName());
       break;
     case COMPLETE:
       if (!message.getPhase().equals(ForgeClientHandshakePhase.WAITING_SERVER_COMPLETE)) {
         session.disconnect(
             "Retrieved unexpected forge handshake ack message. (Got "
                 + message.getPhase()
                 + ", expected "
                 + ForgeClientHandshakePhase.WAITING_SERVER_COMPLETE
                 + ")");
       } else {
         session.send(new MessageForgeHandshakeInOutAck(ForgeServerHandshakePhase.COMPLETE));
         phase.set(ForgeServerHandshakePhase.DONE);
       }
       LanternGame.log()
           .info(
               "{}: Forge handshake -> Received ack (waitingServerComplete) message.",
               session.getGameProfile().getName());
       break;
     case DONE:
       if (!message.getPhase().equals(ForgeClientHandshakePhase.PENDING_COMPLETE)
           && !message.getPhase().equals(ForgeClientHandshakePhase.COMPLETE)) {
         session.disconnect(
             "Retrieved unexpected forge handshake ack message. (Got "
                 + message.getPhase()
                 + ", expected "
                 + ForgeClientHandshakePhase.PENDING_COMPLETE
                 + " or "
                 + ForgeClientHandshakePhase.COMPLETE
                 + ")");
       } else {
         if (message.getPhase().equals(ForgeClientHandshakePhase.PENDING_COMPLETE)) {
           session.send(new MessageForgeHandshakeInOutAck(ForgeServerHandshakePhase.DONE));
           LanternGame.log()
               .info(
                   "{}: Forge handshake -> Received ack (pendingComplete) message.",
                   session.getGameProfile().getName());
         } else {
           session.setProtocolState(ProtocolState.PLAY);
           session.spawnPlayer();
           LanternGame.log()
               .info(
                   "{}: Forge handshake -> Received ack (complete) message.",
                   session.getGameProfile().getName());
         }
       }
       break;
     case ERROR:
       break;
     default:
       session.disconnect(
           "Retrieved unexpected forge handshake ack message. (Got " + message.getPhase() + ")");
   }
 }
  @Override
  public void handle(NetworkContext context, MessageLoginInEncryptionResponse message) {
    Session session = context.getSession();
    PrivateKey privateKey = session.getServer().getKeyPair().getPrivate();

    // Create rsaCipher
    Cipher rsaCipher;
    try {
      rsaCipher = Cipher.getInstance("RSA");
    } catch (GeneralSecurityException e) {
      LanternGame.log().error("Could not initialize RSA cipher", e);
      session.disconnect("Unable to initialize RSA cipher.");
      return;
    }

    // Decrypt shared secret
    SecretKey sharedSecret;
    try {
      rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
      sharedSecret = new SecretKeySpec(rsaCipher.doFinal(message.getSharedSecret()), "AES");
    } catch (Exception e) {
      LanternGame.log().warn("Could not decrypt shared secret", e);
      session.disconnect("Unable to decrypt shared secret.");
      return;
    }

    // Decrypt verify token
    byte[] verifyToken;
    try {
      rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
      verifyToken = rsaCipher.doFinal(message.getVerifyToken());
    } catch (Exception e) {
      LanternGame.log().warn("Could not decrypt verify token", e);
      session.disconnect("Unable to decrypt verify token.");
      return;
    }

    // Check verify token
    if (!Arrays.equals(verifyToken, session.getVerifyToken())) {
      session.disconnect("Invalid verify token.");
      return;
    }

    // Initialize stream encryption
    session.setEncryption(sharedSecret);

    // Create hash for auth
    String hash;
    try {
      MessageDigest digest = MessageDigest.getInstance("SHA-1");
      String sessionId = context.getChannel().attr(HandlerLoginStart.SESSION_ID).getAndRemove();

      digest.update(sessionId.getBytes());
      digest.update(sharedSecret.getEncoded());
      digest.update(session.getServer().getKeyPair().getPublic().getEncoded());

      // BigInteger takes care of sign and leading zeroes
      hash = new BigInteger(digest.digest()).toString(16);
    } catch (NoSuchAlgorithmException e) {
      LanternGame.log().error("Unable to generate SHA-1 digest", e);
      session.disconnect("Failed to hash login data.");
      return;
    }

    // Start auth thread
    Thread clientAuthThread =
        new Thread(new ClientAuthRunnable(session, session.getVerifyUsername(), hash));
    clientAuthThread.setName("ClientAuth{" + session.getVerifyUsername() + "}");
    clientAuthThread.start();
  }