All Articles

recover cvs pserver passwords

Being a very forgetful person, I relied upon my encoded cvs password too much lately and had never really bothered to keep a copy of the clear text; until I had to use Tortoise for some cvs operation. I’ve always meant to be sorting that little negligence on my part out but I never actually got round to it… As I was waiting for the support people to reset my password, I did some research on cvs’ pserver password scrambling algorithm and I found that all it does is a simple mapping, described in detail in section 6 of The CVS Client/Server Protocol. So, for example, % will be mapped to m and m can be decoded by getting the inverse mapping back to %, as simple as that. Officially, only a subset of characters can be used for passwords and encoded passwords, see discussion in section 8. Based on that, I wrote a java class for encoding and decoding cvs passwords, using pserver authentication protocol’s scrambling algorithm. I’d been too slow; by that time my password had already been reset by our support team.

/**
 * A simple class for encoding and decoding passwords for cvs'
 * pserver protocol. Can be used to recover forgotten passwords.
 */
public class CvsPassword {

    private static char[] aChars = {0, 0, 0, 0, 0, 0, 0, 0,
                                    0, 0, 0, 0, 0, 0, 0, 0,
                                    0, 0, 0, 0, 0, 0, 0, 0,
                                    0, 0, 0, 0, 0, 0, 0, 0,
                                    114, 120, 53, 79, 0, 109, 72, 108,
                                    70, 64, 76, 67, 116, 74, 68, 87,
                                    111, 52, 75, 119, 49, 34, 82, 81,
                                    95, 65, 112, 86, 118, 110, 122, 105,
                                    41, 57, 83, 43, 46, 102, 40, 89,
                                    38, 103, 45, 50, 42, 123, 91, 35,
                                    125, 55, 54, 66, 124, 126, 59, 47,
                                    92, 71, 115, 78, 88, 107, 106, 56,
                                    0, 121, 117, 104, 101, 100, 69, 73,
                                    99, 63, 94, 93, 39, 37, 61, 48,
                                    58, 113, 32, 90, 44, 98, 60, 51,
                                    33, 97, 62, 77, 84, 80, 85};


    /**
     * Encodes a cvs password to be used in .cvspass file. Throws an
     * exception if sClear is null, if a character is found outside
     * the 0 - 126 range, or if within the range, one of the
     * non-allowed characters.
     *
     * @param sClear the password in clear to be encoded
     * @return the encoded cvs password
     */
    public static String encode(String sClear) {
        char[] acScrambled = new char[sClear.length() + 1];

        acScrambled[0] = 'A';
        for (int i = 1; i < acScrambled.length; i++) {
            char c = sClear.charAt(i - 1);
            if (c == '`' || c == '$' || c < 32)
                throw new IllegalArgumentException(
                        "Illegal character was found in clear password.");
            acScrambled[i] = aChars[c];
        }

        return String.valueOf(acScrambled);
    }

   /**
     * Recovers an encoded via pserver protocol cvs password.
     * Throws an exception if passed a null input.
     *
     * @param sScrambled the encoded password to be decoded
     * @return the decoded password
     */
    public static String decode(String sScrambled) {
        if (sScrambled.equals(""))
            return "";
        return encode(sScrambled.substring(1)).substring(1);
    }

    public static void main(String[] sArgs) {
        // Encode password and decode the result.
        System.out.println(CvsPassword.encode("password"));
        System.out.println(CvsPassword.decode("A:yZZ30 e"));

        // Print the character mapping used for the
        // password scrambling.
        for (int i = 33; i < aChars.length; i++) {
            System.out.print(i + "(" + (char) i + ")->");
            System.out.print((int) aChars[i] + "(" + aChars[i] + ") ");
        }
    }
}