采用256位密钥的计数器模式的Android AES

时间:2016-01-29 08:45:20

标签: android encryption aes

我们的团队根据Javascript代码段加密数据,请点击此页http://www.movable-type.co.uk/scripts/aes.html和此处 http://anh.cs.luc.edu/331/code/aes.py

现在我必须使用移动设备上的java解密数据。

页面上写着:

  

此脚本中的密钥是通过应用密码例程来加密密码的前16/24/32个字符(用于128- / 192- / 256位密钥)来获得密钥。这是在完全自包含的脚本中获得安全密钥的便捷方式(在生产环境中,与本质上的教程代码相反,密钥可以生成为散列,例如简单地为key = Sha256(密码))。更详细地说,提供的密码将转换为UTF-8(为字节安全),然后将前16/24/32个字符转换为字节。生成的pwBytes用作Aes.keyExpansion()的种子,然后将其用作使用Aes.cipher()加密pwBytes的密钥。以这种方式从(不切实际的)简单的`

生成的密钥的示例

所以我觉得我在使用密码生成密钥时遇到了困难

这是我的测试用例:

  • 原文:Today
  • 密码:aa2145f9e2a5daaa9c6a8ddc5f5c1a39
  • 执行结果j��

他们没有两次使用密钥,他们每次都使用随机初始化矢量(IV),因此结果不同。

Java代码:

private static String decrypt(SecretKey aesKey, String encodedCiphertext) {
    try {
        // that's no base 64, that's base 64 over the UTF-8 encoding of the code points
        byte[] ciphertext = jsBase64Decode(encodedCiphertext);
        Cipher aesCTR = Cipher.getInstance("AES/CTR/NOPADDING");
        int n = aesCTR.getBlockSize();
        byte[] counter = new byte[n];
        int nonceSize = n / 2;
        System.arraycopy(ciphertext, 0, counter, 0, nonceSize);
        IvParameterSpec iv = new IvParameterSpec(counter);
        aesCTR.init(Cipher.DECRYPT_MODE, aesKey, iv);
        byte[] plaintext = aesCTR.doFinal(ciphertext, nonceSize, ciphertext.length - nonceSize);
        return new String(plaintext, "UTF-8");
        // that's no base 64, that's base 64 over the UTF-8 encoding of the code points

    } catch (Exception e) {
        e.printStackTrace();
    }
    return "";
}

private static byte[] jsBase64Decode(String encodedCiphertext) {
    byte[] ciphertext = null;
    try {
        byte[] utf8CT = Base64.decode(encodedCiphertext);

        String cts = new String(utf8CT, "UTF-8");
        ciphertext = new byte[cts.length()];
        for (int i = 0; i < cts.length(); i++) {
            ciphertext[i] = (byte) (cts.charAt(i) & 0xFF);
        }

    }catch (Exception e) {
        e.printStackTrace();
    }
    //Arrays.copyOfRange(new byte[100], 0, 99);
    return ciphertext;
}

// that should not be a singleton lazybones, it may contain state
private static SecretKey deriveKey(String password, int nBits) throws CharacterCodingException {
    try {
        Charset charset = Charset.forName("UTF-8");
        CharsetEncoder encoder = charset.newEncoder();

        ByteBuffer buf = encoder.encode(CharBuffer.wrap(password));
        //byte[] buf1 = password.getBytes();
        int nBytes = nBits / Byte.SIZE; // bits / Byte.SIZE;
        Cipher aesECB = Cipher.getInstance("AES/ECB/NoPadding");
        int n = aesECB.getBlockSize();
        byte[] pwBytes = new byte[nBytes];
        // so we only use those characters that fit in nBytes! oops!
        buf.get(pwBytes, 0, buf.remaining());
        //pwBytes = password.getBytes("UTF-8");
        SecretKey derivationKey = new SecretKeySpec(pwBytes, "AES");
        aesECB.init(Cipher.ENCRYPT_MODE, derivationKey);
        // and although the derivationKey is nBytes in size, we only encrypt 16 (the block size)
        byte[] partialKey = aesECB.doFinal(pwBytes, 0, n);
        byte[] key = new byte[nBytes];
        System.arraycopy(partialKey, 0, key, 0, n);
        // but now we have too few so we *copy* key bytes
        // so only the increased number of rounds is configured using nBits
        System.arraycopy(partialKey, 0, key, n, nBytes - n);
        SecretKey derivatedKey = new SecretKeySpec(key, "AES");
        return derivatedKey;
    } catch (Exception e) {
        throw new IllegalStateException("Key derivation should always finish", e);
    }
}


public static String main(){

    SecretKey key = null;
    try {
        key = deriveKey("aa2145f9e2a5daaa9c6a8ddc5f5c1a39", 256);


    } catch (Exception e) {
        e.printStackTrace();
    }
    // ciphertext may vary in length depending on UTF-8 encoding
    String pt = decrypt(key, "eQDH+srPqlbh7Ml42g==");
    return pt;
}

1 个答案:

答案 0 :(得分:0)

我自己移植了。无论如何还是Tks。

在这里发帖给任何想要像我一样的人

    /**
 * Created by luu on 1/28/2016.
 */
public class AES {


// sBox is pre-computed multiplicative inverse in GF(2^8) used in subBytes and keyExpansion [§5.1.1]
private static final int[] sBox =  new int[]{
            0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
            0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
            0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
            0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
            0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
            0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
            0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
            0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
            0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
            0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
            0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
            0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
            0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
            0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
            0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
            0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
        };

// rCon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
private static final int[][] rCon = new int[][]{
        new int[]{0x00, 0x00, 0x00, 0x00},//0
        new int[]{0x01, 0x00, 0x00, 0x00},//1
        new int[]{0x02, 0x00, 0x00, 0x00},//2
        new int[]{0x04, 0x00, 0x00, 0x00},//3
        new int[]{0x08, 0x00, 0x00, 0x00},//4
        new int[]{0x10, 0x00, 0x00, 0x00},//5
        new int[]{0x20, 0x00, 0x00, 0x00},//6
        new int[]{0x40, 0x00, 0x00, 0x00},//7
        new int[]{0x80, 0x00, 0x00, 0x00},//8
        new int[]{0x1b, 0x00, 0x00, 0x00},//9
        new int[]{0x36, 0x00, 0x00, 0x00}//10
};

/**
 * AES Cipher function: encrypt 'input' state with Rijndael algorithm [§5.1];
 *   applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage.
 *
 * @param   {number[]}   input - 16-byte (128-bit) input state array.
 * @param   {number[][]} w - Key schedule as 2D byte-array (Nr+1 x Nb bytes).
 * @returns {number[]}   Encrypted output state array.
 */
private static int[] cipher (int[] input, int[][] w) {
    int Nb = 4;               // block size (in words): no of columns in state (fixed at 4 for AES)
    int Nr = w.length / Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys

    int[][] state = new int[4][];//  [[],[],[],[]];  // initialise 4xNb byte-array 'state' with input [§3.4]
    for (int i = 0; i < 4 * Nb; i++)
    {
        if (state[i % 4] == null)
            state[i % 4] = new int[4];
        state[i % 4][(int)Math.floor((double) i / 4)] = input[i];
    }

    state = addRoundKey(state, w, 0, Nb);

    for (int round = 1; round < Nr; round++)
    {
        state = subBytes(state, Nb);
        state = shiftRows(state, Nb);
        state = mixColumns(state, Nb);
        state = addRoundKey(state, w, round, Nb);
    }

    state = subBytes(state, Nb);
    state = shiftRows(state, Nb);
    state = addRoundKey(state, w, Nr, Nb);

    int[] output = new int[4 * Nb];  // convert state to 1-d array before returning [§3.4]
    for (int i = 0; i < 4 * Nb; i++)
        output[i] = state[i % 4][(int)Math.floor((double) i / 4)];

    return output;
};


/**
 * Perform key expansion to generate a key schedule from a cipher key [§5.2].
 *
 * @param   {number[]}   key - Cipher key as 16/24/32-byte array.
 * @returns {number[][]} Expanded key schedule as 2D byte-array (Nr+1 x Nb bytes).
 */
private static int[][] keyExpansion (int[] key) {
    int Nb = 4;            // block size (in words): no of columns in state (fixed at 4 for AES)
    int Nk = key.length / 4; // key length (in words): 4/6/8 for 128/192/256-bit keys
    int Nr = Nk + 6;       // no of rounds: 10/12/14 for 128/192/256-bit keys

    int[][] w = new int[Nb * (Nr + 1)][Nb];
    int[] temp = new int[4];

    // initialise first Nk words of expanded key with cipher key
    for (int i = 0; i < Nk; i++) {
        int[] r = new int[] { key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3] };
        w[i] = r;
    }

    // expand the key into the remainder of the schedule
    for (int i = Nk; i < (Nb * (Nr + 1)); i++) {
        w[i] = new int[4];
        for (int t = 0; t < 4; t++) temp[t] = w[i - 1][t];
        // each Nk'th word has extra transformation
        if (i % Nk == 0) {
            temp = subWord(rotWord(temp));
            for (int t = 0; t < 4; t++) temp[t] ^= rCon[i / Nk][t];
        }
        // 256-bit key has subWord applied every 4th word
        else if (Nk > 6 && i % Nk == 4) {
            temp = subWord(temp);
        }
        // xor w[i] with w[i-1] and w[i-Nk]
        for (int t = 0; t < 4; t++)
            w[i][t] = (w[i - Nk][t] ^ temp[t]);
    }

    return w;
};

/**
 * Apply SBox to state S [§5.1.1]
 * @private
 */
private static int[][] subBytes (int[][] s, int Nb) {
    for (int r = 0; r < 4; r++) {
        for (int c = 0; c < Nb; c++) s[r][c] = sBox[s[r][c]];
    }
    return s;
}


/**
 * Shift row r of state S left by r bytes [§5.1.2]
 * @private
 */
private static int[][] shiftRows (int[][] s, int Nb) {
    int[] t = new int[4];
    for (int r = 1; r < 4; r++) {
        for (int c = 0; c < 4; c++) t[c] = s[r][(c + r) % Nb];  // shift into temp copy
        for (int c = 0; c < 4; c++) s[r][c] = t[c];         // and copy back
    }          // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
    return s;  // see asmaes.sourceforge.net/rijndael/rijndaelImplementation.pdf
}


/**
 * Combine bytes of each col of state S [§5.1.3]
 * @private
 */
private static int[][] mixColumns (int[][] s, int Nb) {
    for (int c = 0; c < 4; c++) {
        int[] a = new int[4];  // 'a' is a copy of the current column from 's'
        int[] b = new int[4];  // 'b' is a•{02} in GF(2^8)
        for (int i = 0; i < 4; i++)
        {
            a[i] = s[i][c];
            b[i] = (s[i][c] & 0x80) > 0 ? (s[i][c] << 1 ^ 0x011b) : (s[i][c] << 1);
        }
        // a[n] ^ b[n] is a•{03} in GF(2^8)
        s[0][c] = (b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]); // {02}•a0 + {03}•a1 + a2 + a3
        s[1][c] = (a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]); // a0 • {02}•a1 + {03}•a2 + a3
        s[2][c] = (a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]); // a0 + a1 + {02}•a2 + {03}•a3
        s[3][c] = (a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]); // {03}•a0 + a1 + a2 + {02}•a3
    }
    return s;
}


/**
 * Xor Round Key into state S [§5.1.4]
 * @private
 */
private static int[][] addRoundKey (int[][] state, int[][] w, int rnd, int Nb) {
    for (int r = 0; r < 4; r++) {
        for (int c = 0; c < Nb; c++) state[r][c] ^= w[rnd * 4 + c][r];
    }
    return state;
}


/**
 * Apply SBox to 4-byte word w
 * @private
 */
private static int[] subWord (int[] w) {
    for (int i = 0; i < 4; i++) w[i] = sBox[w[i]];
    return w;
}


/**
 * Rotate 4-byte word w left by one byte
 * @private
 */
private static int[] rotWord (int[] w) {
    int tmp = w[0];
    for (int i = 0; i < 3; i++) w[i] = w[i + 1];
    w[3] = tmp;
    return w;
}

/**
 * Decrypt a text encrypted by AES in counter mode of operation
 *
 * @param   {string} ciphertext - Source text to be encrypted.
 * @param   {string} password - Password to use to generate a key.
 * @param   {number} nBits - Number of bits to be used in the key; 128 / 192 / 256.
 * @returns {string} Decrypted text
 *
 * @example
 *   var decr = Aes.Ctr.encrypt('lwGl66VVwVObKIr6of8HVqJr', 'pāşšŵōřđ', 256); // decr: 'big secret'
 */
public static String decrypt(String ciphertext, String password, int nBits) throws Exception{
    String plaintext = "";
    //try {
        int blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
        if (!(nBits == 128 || nBits == 192 || nBits == 256))
            return ""; // standard allows 128/192/256 bit keys

        ciphertext = base64Decoder(ciphertext);
        password = UTF8Encode(password);

        // use AES to encrypt password (mirroring encrypt routine)
        int nBytes = nBits / 8;  // no bytes in key
        int[] pwBytes = new int[nBytes];
        for (int i = 0; i < nBytes; i++) {
            pwBytes[i] = Float.isNaN(password.charAt(i)) ? 0 : password.charAt(i);
        }

        int[] key = cipher(pwBytes, keyExpansion(pwBytes));
        // expand key to 16/24/32 bytes long
        int bytesExpand = nBytes - 16;
        if(bytesExpand > 0){

            int keyOriginalLength = key.length;
            int[] expandKey = new int[bytesExpand];
            int[] endKey = new int[keyOriginalLength + bytesExpand];
            System.arraycopy(key, 0, expandKey, 0, bytesExpand);// initial expandKey
            System.arraycopy(key, 0, endKey, 0, key.length);// copy all from key to endKey
            System.arraycopy(expandKey, 0, endKey, key.length, expandKey.length);
            key = endKey;
        }

        // recover nonce from 1st 8 bytes of ciphertext
        int[] counterBlock = new int[16];
        String ctrTxt = ciphertext.substring(0, 8);
        for (int i = 0; i < 8; i++) counterBlock[i] = ctrTxt.charAt(i);

        // generate key schedule
        int[][] keySchedule = keyExpansion(key);

        // separate ciphertext into blocks (skipping past initial 8 bytes)
        int nBlocks = (int) Math.ceil((ciphertext.length() - 8) / (float)blockSize);
        String[] cipherArr = new String[nBlocks];
        for (int b = 0; b < nBlocks; b++) {
            int start = 8 + b * blockSize;
            int end =   8 + b * blockSize + blockSize;
            if (end >= ciphertext.length())
                cipherArr[b] = UTF8Encode(ciphertext.substring(start));
            else
                cipherArr[b] = UTF8Encode(ciphertext.substring(start, end));
        }
        // ciphertext is now array of block-length strings,  ³F.àiþ±wãì¿,ß°d
        // plaintext will get generated block-by-block into "³F.àiþ±wãì¿,ß°" array of block-length strings
        String[] plaintxt = new String[cipherArr.length];
        // Expand CounterBlock
        for (int b = 0; b < nBlocks; b++) {
            // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
            for (int c = 0; c < 4; c++)
                counterBlock[15 - c] = (b >> c * 8) & 0xff;
            for (int c = 0; c < 4; c++)
                // counterBlock[15 - c - 4] = (b / 0x100000000 >>> c * 8);
                counterBlock[15 - c - 4] = 0;

            int[] cipherCntr = cipher(counterBlock, keySchedule);  // encrypt counter block
            char[] plaintxtByte = new char[cipherArr[b].length()];
            for (int i = 0; i < cipherArr[b].length(); i++) {

                    plaintxtByte[i] = (char) (cipherCntr[i] ^ cipherArr[b].charAt(i));


            }
            plaintxt[b] = String.copyValueOf(plaintxtByte);
        }

        // join array of blocks into single plaintext string
        plaintext = joinArray(plaintxt);//   plaintxt.Join('');

        // join array of blocks into single plaintext string
        plaintext = UTF8Decode(plaintext);// decode from UTF8 back to Unicode multi-byte chars

    return plaintext;
};

private static String joinArray(Object[] source){
    String dest = "";
    for(int i = 0; i< source.length; i++){
        dest += (String)source[i];
    }
    return dest;
}

private static String UTF8Decode(String s) throws Exception {

    byte[] utf8Bytes = new byte[s.length()];
    for (int i = 0; i < s.length(); ++i)
    {
        //Debug.Assert( 0 <= utf8String[i] && utf8String[i] <= 255, "the char must be in byte's range");
        utf8Bytes[i] = (byte)s.charAt(i);
    }

    return new String(utf8Bytes, UTF8);
}

public static String base64Decoder(String data) throws Exception {

    byte[] b = Base64.decode(data.getBytes("ISO-8859-1"), Base64.NO_WRAP);
    return new String(b, "ISO-8859-1");
}

private static final String UTF8 = "UTF-8";

private static String UTF8Encode(String s) throws UnsupportedEncodingException {

    return new String(s.getBytes(UTF8), UTF8);
}

}