Package hamsam.protocol.yahoo

Source Code of hamsam.protocol.yahoo.Crypt

/*
* Hamsam - Instant Messaging API
* Copyright (C) 2003 Raghu K
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package hamsam.protocol.yahoo;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

class Crypt {
  /**
   * Encryption method indicator
   */
  private int method;

  private static final char[] base64Digits = {
      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
      'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
      'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
      'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
      'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
      'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
      '8', '9', '.', '_' };
  private String yahooId, password;
  char[] seed;

  public Crypt(String yahooId, String password, String seed)
    throws NullPointerException {
    if (seed == null)
      throw new NullPointerException("seed is null");
    if (yahooId == null)
      throw new NullPointerException("yahooId is null");
    this.yahooId = new String(yahooId);
    this.password = new String(password);
    this.seed = seed.toCharArray();
  }

  public String[] doEncrypt() {
    try {
      if (this.method == 1)
        return do0x0bEncrypt();
      else
        return doPre0x0bEncrypt();
    } catch (NoSuchAlgorithmException e) {
      return null;
    }
  }

  /**
   * The new encryption introduced in 0x0b protocol. Thanks to Gaim
   * developers for cracking this so fast.
   */
  private String[] do0x0bEncrypt() throws NoSuchAlgorithmException {
    char[] magic = new char[64];

    int magicLength = do0x0bMagic1(magic);
    do0x0bMagic2(magicLength, magic);
    byte[] magicKeyChar = do0x0bMagic3(magicLength, magic);

    // Get password and crypt hashes as per usual.
    MessageDigest md5 = MessageDigest.getInstance("MD5");
    md5.update(password.getBytes());
    char[] result = Util.byteArrayToCharArray(md5.digest());
    char[] passwordHash = toBase64(result);

    md5.reset();
    byte[] cryptResult = crypt(password, "$1$_2S43d5f$");
    md5.update(cryptResult);
    result = Util.byteArrayToCharArray(md5.digest());
    char[] cryptHash = toBase64(result);

    // Our first authentication response is based off the password hash.
    String resp6 = get0x0bResponseString(passwordHash, magicKeyChar);
    // Our second authentication response is based off the crypto hash.
    String resp96 = get0x0bResponseString(cryptHash, magicKeyChar);

    String ret[] = new String[2];
    ret[0] = resp6.toString();
    ret[1] = resp96.toString();
    return ret;
  }

  /*
   * Magic: Phase 1.  Generate what seems to be a 30
   * byte value (could change if base64
   * ends up differently?  I don't remember and I'm
   * tired, so use a 64 byte buffer.
   */
  private int do0x0bMagic1(char[] magic) {
    String challengeLookup = "qzec2tb3um1olpar8whx4dfgijknsvy5";
    String operandLookup = "+|&%/*^-";
    int magicCount = 0;
    int magicWork = 0;

    for (int magicPtr = 0; magicPtr < seed.length; magicPtr++) {
      // Ignore parentheses.
      if (seed[magicPtr] == '(' || seed[magicPtr] == ')')
        continue;

      // Characters and digits verify against the challenge lookup.
      if (Character.isLetterOrDigit(seed[magicPtr])) {
        int loc = challengeLookup.indexOf(seed[magicPtr]);
        if (loc == -1) {
          // This isn't good
          continue;
        }

        // Get offset into lookup table and lsh 3.
        magicWork = loc << 3;
        continue;
      } else {
        int loc = operandLookup.indexOf(seed[magicPtr]);
        if (loc == -1) {
          // Also not good.
          continue;
        }

        // Oops; how did this happen?
        if (magicCount >= 64)
          break;

        magic[magicCount++] = (char) (magicWork | loc);
        continue;
      }
    }

    return magicCount;
  }

  /* Magic: Phase 2.  Take generated magic value and sprinkle fairy dust on the values. */
  private void do0x0bMagic2(int magicLength, char[] magic) {
    for (int magicCount = magicLength - 2; magicCount >= 0; magicCount--) {
      char byte1;
      char byte2;

      // Bad.  Abort.
      if (magicCount >= magicLength)
        break;

      byte1 = magic[magicCount];
      byte2 = magic[magicCount + 1];

      byte1 *= 0xcd;
      byte1 ^= byte2;

      magic[magicCount + 1] = byte1;
    }
  }

  /* Magic: Phase 3.  Final phase; this gives us our key. */
  private byte[] do0x0bMagic3(int magicLength, char[] magic)
    throws NoSuchAlgorithmException {
    int magicCount = 1;
    int dump[] = new int[20];

    for (int index = 0; index < 20;) {
      int bl = 0;
      int cl = magic[magicCount++];

      if (magicCount >= magicLength)
        break;

      if (cl > 0x7F) {
        if (cl < 0xe0)
          bl = cl = (cl & 0x1f) << 6;
        else {
          bl = magic[magicCount++];
          cl = (cl & 0x0f) << 6;
          bl = ((bl & 0x3f) + cl) << 6;
        }

        cl = magic[magicCount++];
        bl = (cl & 0x3f) + bl;
      } else
        bl = cl;

      dump[index++] = (bl & 0xff00) >>> 8;
      dump[index++] = bl & 0xff;
    }

    // First four bytes are magic key.
    byte[] chal = new byte[7];
    byte magicKeyChar[] = new byte[4];
    for (int i = 0; i < 4; i++)
      chal[i] = magicKeyChar[i] = (byte) dump[i];

    // Compute values for recursive function table!
    boolean done = false;
    byte compare[] = new byte[16];
    for (int i = 0; i < 16; i++)
      compare[i] = (byte) dump[i + 4];

    int table = 0;
    int depth = 0;
    for (int i = 0; i < 0xffff && !done; i++) {
      for (int j = 0; j < 5 && !done; j++) {
        chal[4] = (byte) i;
        chal[5] = (byte) (i >> 8);
        chal[6] = (byte) j;
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        md5.update(chal);
        byte[] result = md5.digest();
        if (Arrays.equals(compare, result)) {
          depth = i;
          table = j;
          done = true;
        }
      }
    }

    // Transform magicKeyChar using transform table
    int value = convertToInt(magicKeyChar);
    value = YahooTransformation.transfrom(table, depth, value);
    value = YahooTransformation.transfrom(table, depth, value);
    magicKeyChar[0] = (byte) (value & 0xff);
    magicKeyChar[1] = (byte) ((value >> 8) & 0xff);
    magicKeyChar[2] = (byte) ((value >> 16) & 0xff);
    magicKeyChar[3] = (byte) ((value >> 24) & 0xff);
    return magicKeyChar;
  }

  private int convertToInt(byte[] bytes) {
    long ret = 0;
    for (int i = 3; i >= 0; i--) {
      ret <<= 8;
      if (bytes[i] >= 0)
        ret |= bytes[i];
      else
        ret |= (256 + bytes[i]);
    }
    return (int) ret;
  }

  private String get0x0bResponseString(char[] hash, byte[] magicKeyChar)
    throws NoSuchAlgorithmException {
    byte[] hashXOR1 = new byte[64];
    byte[] hashXOR2 = new byte[64];
    int x, cnt = 0;
    for (x = 0; x < hash.length; x++)
      hashXOR1[cnt++] = (byte) (hash[x] ^ 0x36);
    for (x = cnt; x < hashXOR1.length; x++)
      hashXOR1[x] = 0x36;

    cnt = 0;
    for (x = 0; x < hash.length; x++)
      hashXOR2[cnt++] = (byte) (hash[x] ^ 0x5c);
    for (x = cnt; x < hashXOR2.length; x++)
      hashXOR2[x] = 0x5c;

    MessageDigest sha1 = MessageDigest.getInstance("SHA");
    MessageDigest sha2 = MessageDigest.getInstance("SHA");

    /* The first context gets the password hash XORed with 0x36 plus a magic
     * value which we previously extrapolated from our challenge. */
    sha1.update(hashXOR1);
    sha1.update(magicKeyChar);
    byte[] digest1 = sha1.digest();

    /* The second context gets the password hash XORed
     * with 0x5c plus the SHA-1 digest of the first context. */
    sha2.update(hashXOR2);
    sha2.update(digest1);
    char[] digest2 = Util.byteArrayToCharArray(sha2.digest());

    /* Now that we have digest2, use it to fetch characters from
     * an alphabet to construct our first authentication response. */
    char[] alphabet1 = "FBZDWAGHrJTLMNOPpRSKUVEXYChImkwQ".toCharArray();
    char[] alphabet2 = "F0E1D2C3B4A59687abcdefghijklmnop".toCharArray();
    char[] delimitLookup = ",;".toCharArray();
    StringBuffer response = new StringBuffer();
    for (x = 0; x < 20; x += 2) {
      int val = 0;
      int lookup = 0;

      // First two bytes of digest stuffed together.
      val = digest2[x];
      val <<= 8;
      val += digest2[x + 1] & 0xff;

      lookup = (val >> 0x0b);
      lookup &= 0x1f;
      if (lookup >= alphabet1.length)
        break;
      response.append(alphabet1[lookup]);
      response.append('=');

      lookup = (val >> 0x06);
      lookup &= 0x1f;
      if (lookup >= alphabet2.length)
        break;
      response.append(alphabet2[lookup]);

      lookup = (val >> 0x01);
      lookup &= 0x1f;
      if (lookup >= alphabet2.length)
        break;
      response.append(alphabet2[lookup]);

      lookup = (val & 0x01);
      if (lookup >= delimitLookup.length)
        break;
      response.append(delimitLookup[lookup]);
    }

    return response.toString();
  }

  private String[] doPre0x0bEncrypt() throws NoSuchAlgorithmException {
    MessageDigest md5 = MessageDigest.getInstance("MD5");

    md5.update(password.getBytes());
    char[] result = Util.byteArrayToCharArray(md5.digest());
    char[] passwordHash = toBase64(result);

    md5.reset();
    byte[] cryptResult = crypt(password, "$1$_2S43d5f$");
    md5.update(cryptResult);
    result = Util.byteArrayToCharArray(md5.digest());
    char[] cryptHash = toBase64(result);

    int sv = seed[15] % 8;
    char checksum = 0;
    String hashStringP = null, hashStringC = null;
    switch (sv % 5) {
      case 0 :
        checksum = (char) (seed[seed[7] % 16] & 0xff);
        hashStringP =
          String.valueOf(checksum)
            + new String(passwordHash)
            + yahooId
            + new String(seed);
        hashStringC =
          String.valueOf(checksum)
            + new String(cryptHash)
            + yahooId
            + new String(seed);
        break;
      case 1 :
        checksum = (char) (seed[seed[9] % 16] & 0xff);
        hashStringP =
          String.valueOf(checksum)
            + yahooId
            + new String(seed)
            + new String(passwordHash);
        hashStringC =
          String.valueOf(checksum)
            + yahooId
            + new String(seed)
            + new String(cryptHash);
        break;
      case 2 :
        checksum = (char) (seed[seed[15] % 16] & 0xff);
        hashStringP =
          String.valueOf(checksum)
            + new String(seed)
            + new String(passwordHash)
            + yahooId;
        hashStringC =
          String.valueOf(checksum)
            + new String(seed)
            + new String(cryptHash)
            + yahooId;
        break;
      case 3 :
        checksum = (char) (seed[seed[1] % 16] & 0xff);
        hashStringP =
          String.valueOf(checksum)
            + yahooId
            + new String(passwordHash)
            + new String(seed);
        hashStringC =
          String.valueOf(checksum)
            + yahooId
            + new String(cryptHash)
            + new String(seed);
        break;
      case 4 :
        checksum = (char) (seed[seed[3] % 16] & 0xff);
        hashStringP =
          String.valueOf(checksum)
            + new String(passwordHash)
            + new String(seed)
            + yahooId;
        hashStringC =
          String.valueOf(checksum)
            + new String(cryptHash)
            + new String(seed)
            + yahooId;
        break;
    }

    md5.reset();
    md5.update(hashStringP.getBytes());
    result = Util.byteArrayToCharArray(md5.digest());
    char[] result6 = toBase64(result);

    md5.reset();
    md5.update(hashStringC.getBytes());
    result = Util.byteArrayToCharArray(md5.digest());
    char[] result96 = toBase64(result);

    String ret[] = new String[2];
    ret[0] = new String(result6);
    ret[1] = new String(result96);
    return ret;
  }

  private char[] toBase64(char[] in) {
    int len = in.length;
    char[] out = new char[24];
    int index = 0;

    int i = 0;
    for (; len >= 3; len -= 3) {
      out[index++] = base64Digits[in[i] >>> 2];
      out[index++] =
        base64Digits[((in[i] << 4) & 0x30) | (in[i + 1] >>> 4)];
      out[index++] =
        base64Digits[((in[i + 1] << 2) & 0x3c) | (in[i + 2] >>> 6)];
      out[index++] = base64Digits[in[i + 2] & 0x3f];
      i += 3;
    }

    if (len > 0) {
      char fragment;

      out[index++] = base64Digits[in[i] >>> 2];
      fragment = (char) ((in[i] << 4) & 0x30);
      if (len > 1)
        fragment |= in[i + 1] >>> 4;
      out[index++] = base64Digits[fragment];
      out[index++] =
        (len < 2) ? '-' : base64Digits[(in[i + 1] << 2) & 0x3c];
      out[index++] = '-';
    }

    return out;
  }

  private byte[] crypt(String key, String salt)
    throws NoSuchAlgorithmException {
    String md5SaltPrefix = "$1$";

    // Find beginning of salt string.  The prefix should normally always
    // be present.  Just in case it is not.
    if (salt.startsWith(md5SaltPrefix))
      // Skip salt prefix.
      salt = salt.substring(md5SaltPrefix.length());

    int saltLen = salt.indexOf('$');
    if (saltLen == -1)
      saltLen = salt.length();
    if (saltLen > 8)
      saltLen = 8;

    // Prepare for the real work.
    MessageDigest md1 = MessageDigest.getInstance("MD5");

    // Add the key string.
    md1.update(key.getBytes());

    // Because the SALT argument need not always have the salt prefix we
    // add it separately.
    md1.update(md5SaltPrefix.getBytes());

    // The last part is the salt string.  This must be at most 8
    // characters and it ends at the first `$' character (for
    // compatibility which existing solutions).
    md1.update(salt.getBytes(), 0, saltLen);

    /* Compute alternate MD5 sum with input KEY, SALT, and KEY.  The
    final result will be added to the first context.  */
    MessageDigest md2 = MessageDigest.getInstance("MD5");

    // Add key.
    md2.update(key.getBytes());

    // Add salt.
    md2.update(salt.getBytes(), 0, saltLen);

    // Add key again.
    md2.update(key.getBytes());

    // Now get result of this (16 bytes) and add it to the other context.
    byte[] altResult = md2.digest();

    // Add for any character in the key one byte of the alternate sum.
    int cnt;
    for (cnt = key.length(); cnt > 16; cnt -= 16)
      md1.update(altResult, 0, 16);
    md1.update(altResult, 0, cnt);

    // For the following code we need a NUL byte.
    altResult[0] = 0;

    /* The original implementation now does something weird: for every 1
    bit in the key the first 0 is added to the buffer, for every 0
    bit the first character of the key.  This does not seem to be
    what was intended but we have to follow this to be compatible.  */
    for (cnt = key.length(); cnt > 0; cnt >>= 1) {
      if ((cnt & 1) != 0)
        md1.update(altResult, 0, 1);
      else
        md1.update(key.getBytes(), 0, 1);
    }

    // Create intermediate result.
    altResult = md1.digest();

    /* Now comes another weirdness.  In fear of password crackers here
    comes a quite long loop which just processes the output of the
    previous round again.  We cannot ignore this here.  */
    for (cnt = 0; cnt < 1000; ++cnt) {
      // New context.
      md1.reset();

      // Add key or last result.
      if ((cnt & 1) != 0)
        md1.update(key.getBytes());
      else
        md1.update(altResult, 0, 16);

      // Add salt for numbers not divisible by 3.
      if (cnt % 3 != 0)
        md1.update(salt.getBytes(), 0, saltLen);

      // Add key for numbers not divisible by 7.
      if (cnt % 7 != 0)
        md1.update(key.getBytes());

      // Add key or last result.
      if ((cnt & 1) != 0)
        md1.update(altResult, 0, 16);
      else
        md1.update(key.getBytes());

      // Create intermediate result.
      altResult = md1.digest();
    }

    // Now we can construct the result string.  It consists of three parts.

    StringBuffer buffer = new StringBuffer();

    buffer.append(md5SaltPrefix);
    buffer.append(salt);

    buffer.append(
      b64From24Bit(altResult[0], altResult[6], altResult[12], 4));
    buffer.append(
      b64From24Bit(altResult[1], altResult[7], altResult[13], 4));
    buffer.append(
      b64From24Bit(altResult[2], altResult[8], altResult[14], 4));
    buffer.append(
      b64From24Bit(altResult[3], altResult[9], altResult[15], 4));
    buffer.append(
      b64From24Bit(altResult[4], altResult[10], altResult[5], 4));
    buffer.append(b64From24Bit((byte) 0, (byte) 0, altResult[11], 2));

    return buffer.toString().getBytes();
  }

  private String b64From24Bit(byte b2, byte b1, byte b0, int count) {
    int i2 = b2 >= 0 ? b2 : 256 + b2;
    int i1 = b1 >= 0 ? b1 : 256 + b1;
    int i0 = b0 >= 0 ? b0 : 256 + b0;
    char[] b64t =
      "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
        .toCharArray();
    String ret = new String();
    int w = (i2 << 16) | (i1 << 8) | i0;
    while (count-- > 0) {
      ret += b64t[w & 0x3f];
      w >>>= 6;
    }

    return ret;
  }

  /**
   * Set the encryption method used by Yahoo. If method is 1, we will use the
   * new method as in version 0x0b, otherwise the old one will be used.
   *
   * @param method 1 indicates new authentication of protocol 0x0b, all other
   *               values indicates old encryption.
   */
  public void setMethod(int method) {
    this.method = method;
  }
}
TOP

Related Classes of hamsam.protocol.yahoo.Crypt

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.