Using Java to get the Administrator password of a Windows EC2 instance

 3 March 2012 -  ~3 Minutes Cloud Computing

Windows instances on Amazon EC2 are given a random password. This post will show how to use Java to retrieve this password.

When starting a new Windows instance in Amazon EC2, the following steps happen on the first boot:

  1. The EC2ConfigService starts up, and generates a random password for the Administrator account.
  2. This password is then encrypted, using the public key part of the keypair given when starting the instance.
  3. The encrypted data is placed into a special part of the instance metadata, where it can be retrieved through the EC2 API GetPasswordData call.

So retrieving the Administrator password should simply be a case invoking GetPasswordData, and applying the private key part of the keypair. As usual, there’s a couple of speed-bumps on the way - firstly, you have to present the decryption key in the correct format, and you have to know exactly the right parameters to give to the decryption engine.

If you ask Amazon to create a keypair for you, or you use a key generated by ssh-keygen, the key you get back is in PEM format. The first problem is that the Java crypto framework can’t load keys in this format. Java can parse keys encoded in DER format. Fortunately there’s not a big difference between the two - PEM is the “ASCII-armoured” equivalent of a DER, consisting of the base-64 encoded DER binary data with an ASCII header and footer (“-----BEGIN...“). To get at the DER data, strip off the header and footer, and base-64 decode it. This can then be used as an input to new PKCS8EncodedKeySpec(...), from which you can ultimately get a PrivateKey object.

The second problem is the decryption parameters - if you get these wrong, all you get back is garbage. The data is encrypted with RSA, with PKCS1 padding - so the magic invocation to give to JCE is: Cipher.getInstance("RSA/NONE/PKCS1Padding").

Putting it all together, here is a complete example. Replace the “X”s and the private key with your own data. This example uses the BouncyCastle crypto provider, but could be adapted to use others.

Download

package uk.co.frontiertown;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.GetPasswordDataRequest;
import com.amazonaws.services.ec2.model.GetPasswordDataResult;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;

import javax.crypto.Cipher;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.spec.PKCS8EncodedKeySpec;

public class GetEc2WindowsAdministratorPassword {

    private static final String ACCESS_KEY = "xxxxxxxxxxxxxxxxxxxx";
    private static final String SECRET_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    private static final String PRIVATE_KEY_MATERIAL = "-----BEGIN RSA PRIVATE KEY-----\n" +
        "MIIEowIBAAKCAQEAjdD54kJ88GxkeRc96EQPL4h8c/7V2Q2QY5VUiJ+EblEdcVnADRa12qkohT4I\n" +
        // several more lines of key data
        "srz+xXTvbjIJ6RL/FDqF8lvWEvb8uSC7GeCMHTznkicwUs0WiFax2AcK3xjgtgQXMgoP\n" +
        "-----END RSA PRIVATE KEY-----\n";

    public static void main(String[] args) throws GeneralSecurityException, InterruptedException {
        Security.addProvider(new BouncyCastleProvider());
        String password = getPassword(ACCESS_KEY, SECRET_KEY, "i-XXXXXXXX", PRIVATE_KEY_MATERIAL);
        System.out.println(password);
    }

    private static String getPassword(String accessKey, String secretKey, String instanceId, String privateKeyMaterial) throws GeneralSecurityException, InterruptedException {

        // Convert the private key in PEM format to DER format, which JCE can understand
        privateKeyMaterial = privateKeyMaterial.replace("-----BEGIN RSA PRIVATE KEY-----\n", "");
        privateKeyMaterial = privateKeyMaterial.replace("-----END RSA PRIVATE KEY-----", "");
        byte[] der = Base64.decode(privateKeyMaterial);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(der);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

        // Get the encrypted password data from EC2
        AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
        AmazonEC2Client client = new AmazonEC2Client(awsCredentials);
        GetPasswordDataRequest getPasswordDataRequest = new GetPasswordDataRequest().withInstanceId(instanceId);
        GetPasswordDataResult getPasswordDataResult = client.getPasswordData(getPasswordDataRequest);
        String passwordData = getPasswordDataResult.getPasswordData();
        while (passwordData == null || passwordData.isEmpty()) {
            System.out.println("No password data - probably not generated yet - waiting and retrying");
            Thread.sleep(10000);
            getPasswordDataResult = client.getPasswordData(getPasswordDataRequest);
            passwordData = getPasswordDataResult.getPasswordData();
        }

        // Decrypt the password
        Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] cipherText = Base64.decode(passwordData);
        byte[] plainText = cipher.doFinal(cipherText);
        String password = new String(plainText, Charset.forName("ASCII"));

        return password;
    }
}

About the author

Richard Downer is a software engineer turned cloud solutions architect, specialising in AWS, and based in Scotland. Richard's interest in technology extends to retro computing and amateur hardware hacking with Raspberry Pi and FPGA.