Recovering Encrypted Eclipse / ZendStudio remote connection FTP and SVN passwords

Introduction

If you do programming or web development with a lot of clients on a freelance basis you will inevitably find yourself in a situation where the login credentials you have for a client’s site are not to be found in your project emails, and naturally the client didn’t keep them themselves.

In this case, if you’ve setup Eclipse (or it’s derivations like Zend Studio) with a remote connection and have told it to save the password as shown in the image below, you can recover the passwords.

 

Save password remote connection eclipse dialog

Save password remote connection eclipse dialog

 

If you don’t want/need to understand the code, you can download the eclipse project here:

Download The Decrypt Eclipse Keyring Java Project

 

Background

The passwords in Eclipse get stored in an encrypted keyring file as per the Eclipse documentation:

RSE uses the Eclipse-supplied keyring file to store passwords. This is the same keyring used by other eclipse services such as the Eclipse Team support. By default, this is stored in the configuration area under org.eclipse.core.runtime/.keyring in an encrypted format. The encryption does not require a password itself so anyone using Eclipse is capable of decrypting this file.

Users can choose to store the keyring in a different file, or provide a password for doing real encryption by using runtime options when starting Eclipse as follows:

eclipse -keyring C:/mykeyring.txt -password mypasswd

Even so, encryption is not particularly strong. So if your workstation is not protected you should remove your passwords and not store them in the future.

To decrypt this keyring file containing the passwords, you will need to create a Java project, add a single decryption class file (below), change one line and run the project.

Source Code

Here is the code of the single file. The line you will need to modify is K in the code listing below. This is taken with minor modifications from this source:

package decrypt;

import java.security.MessageDigest;
import java.util.*;

import java.io.*;

public class DoDecrypt {

	public static void main(String[] args) {
		String s = "/path/to_your/.eclipse_keyring"; // This file may be just .eclipse depending on your setup.

		try {
			InputStream input = new FileInputStream(s);
			try {
				load(input);
			} finally {
				input.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static final int MAGIC_NUMBER = 1;

	private static void load(InputStream is) throws IOException,
			ClassNotFoundException {
		int version = is.read();
		if (version == MAGIC_NUMBER) {
			// read the authorization data
			CipherInputStream cis = new CipherInputStream(is, ""); //$NON-NLS-1$
			ObjectInputStream ois = new ObjectInputStream(cis);
			try {
				Map authorizationInfo = (Hashtable) ois.readObject();
				System.out.println(authorizationInfo);
			} finally {
				ois.close();
			}
		}
	}

	static class CipherInputStream extends FilterInputStream {
		private static final int SKIP_BUFFER_SIZE = 2048;
		private Cipher cipher;

		public CipherInputStream(InputStream is, String password) {
			super(is);
			cipher = new Cipher(Cipher.DECRYPT_MODE, password);
		}

		public boolean markSupported() {
			return false;
		}

		public int read() throws IOException {
			int b = super.read();
			if (b == -1)
				return -1;
			try {
				return (cipher.cipher((byte) b)) & 0x00ff;
			} catch (Exception e) {
				throw new IOException(e.getMessage());
			}
		}

		public int read(byte b[], int off, int len) throws IOException {
			int bytesRead = in.read(b, off, len);
			if (bytesRead == -1)
				return -1;
			try {
				byte[] result = cipher.cipher(b, off, bytesRead);
				for (int i = 0; i < result.length; ++i) 					b[i + off] = result[i]; 				return bytesRead; 			} catch (Exception e) { 				throw new IOException(e.getMessage()); 			} 		} 	  		public long skip(long n) throws IOException { 			byte[] buffer = new byte[SKIP_BUFFER_SIZE]; 	  			int bytesRead = 0; 			long bytesRemaining = n; 	  			while (bytesRead != -1 && bytesRemaining > 0) {
				bytesRead = read(buffer, 0, (int) Math.min(SKIP_BUFFER_SIZE,
						bytesRemaining));
				if (bytesRead > 0) {
					bytesRemaining -= bytesRead;
				}
			}

			return n - bytesRemaining;
		}
	}

	static class Cipher {
		public static final int DECRYPT_MODE = -1;
		public static final int ENCRYPT_MODE = 1;
		private static final int RANDOM_SIZE = 16;

		private int mode = 0;
		private byte[] password = null;

		private byte[] byteStream;
		private int byteStreamOffset;
		private MessageDigest digest;
		private Random random;
		private final byte[] toDigest;

		public Cipher(int mode, String passwordString) {
			this.mode = mode;
			try {
				this.password = passwordString.getBytes("UTF8"); //$NON-NLS-1$
			} catch (UnsupportedEncodingException e) {
				this.password = passwordString.getBytes();
			}
			toDigest = new byte[password.length + RANDOM_SIZE];
		}

		public byte[] cipher(byte[] data) throws Exception {
			return transform(data, 0, data.length, mode);
		}

		public byte[] cipher(byte[] data, int off, int len) throws Exception {
			return transform(data, off, len, mode);
		}

		public byte cipher(byte datum) throws Exception {
			byte[] data = { datum };
			return cipher(data)[0];
		}

		private byte[] generateBytes() throws Exception {
			if (digest == null) {
				digest = MessageDigest.getInstance("SHA"); //$NON-NLS-1$
				// also seed random number generator based on password
				long seed = 0;
				for (int i = 0; i < password.length; i++)
					// this function is known to give good hash distribution for
					// character data
					seed = (seed * 37) + password[i];
				random = new Random(seed);
			}
			// add random bytes to digest array
			random.nextBytes(toDigest);

			// overlay password onto digest array
			System.arraycopy(password, 0, toDigest, 0, password.length);

			// compute and return SHA-1 hash of digest array
			return digest.digest(toDigest);
		}

		private byte[] nextRandom(int length) throws Exception {
			byte[] nextRandom = new byte[length];
			int nextRandomOffset = 0;
			while (nextRandomOffset < length) { 				if (byteStream == null || byteStreamOffset >= byteStream.length) {
					byteStream = generateBytes();
					byteStreamOffset = 0;
				}
				nextRandom[nextRandomOffset++] = byteStream[byteStreamOffset++];
			}
			return nextRandom;
		}

		private byte[] transform(byte[] data, int off, int len, int mod)
				throws Exception {
			byte[] result = nextRandom(len);
			for (int i = 0; i < len; ++i) {
				result[i] = (byte) (data[i + off] + mod * result[i]);
			}
			return result;
		}
	}
}

Java Project Setup

Decrypt Eclipse KeyringYou will need to setup a java project, create a package name: decrypt and then run the project. If you don’t have any compilation errors you will see your decrypted list of sites and passwords (including SVN) in the Eclipse Console tab window.  If you want help setting up a java project in eclipse see this well-documented tutorial.

Console Tab where the passwords will appear

Console Tab where the passwords will appear

  

Completed Project

I’ve included the complete Eclipse java project below, you will need to import this using Eclipse into your editor to get the project setting right.

 

Download the source files for this post

 

Tags:

2 Responses to “Recovering Encrypted Eclipse / ZendStudio remote connection FTP and SVN passwords”

  1. Dave 20. Feb, 2014 at 9:34 am #

    This worked and I got my password once I located the correct path to the .keyring file

  2. ESLEM 18. Aug, 2015 at 3:45 am #

    This works fine, Thank you for posting this.

Leave a Reply