AES Encryption program do not work correctly on IDEs

0

Program

import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class AesCtr256 {

// 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
};
private static final String UTF8 = "UTF-8";

public static void main(String[] args) throws Exception {
    // Security.setProperty("crypto.policy", "unlimited");
    final int maxKeySize = javax.crypto.Cipher.getMaxAllowedKeyLength("AES");
    System.out.println("Max Key Size for AES : " + maxKeySize);

    final String data = "My Encrypted String";
    final String key = "3e6cf9b346544bbb46ec99e33a9e0bcc";
    final String encrypted = encrypt(data, key, 256);
    System.out.println("Encrypted string=" + encrypted);
    System.out.println();
    final String decrypted = decrypt(encrypted, key);
    System.out.println("Decrypted string=" + decrypted);
}

/**
 * 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) {
    final int Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
    final 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);

    final 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) {
    final int Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
    final int Nk = key.length / 4; // key length (in words): 4/6/8 for 128/192/256-bit keys
    final int Nr = Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys

    final 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++) {
        final 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) {
    final 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
        }
        // and copy back
        System.arraycopy(t, 0, s[r], 0, 4);
    } // 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++) {
        final int[] a = new int[4]; // 'a' is a copy of the current column from 's'
        final 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) {
    final int tmp = w[0];
    System.arraycopy(w, 1, w, 0, 3);
    w[3] = tmp;
    return w;
}

public static String encrypt(String plaintext, String password, int nBits) throws Exception {

    final 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
    }
    // note PHP (5) gives us plaintext and password in UTF8 encoding!
    password = UTF8Encode(password);
    plaintext = UTF8Encode(plaintext);
    final int nBytes = nBits / 8; // no bytes in key
    final 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
    final int bytesExpand = nBytes - 16;
    if (bytesExpand > 0) {
        final int keyOriginalLength = key.length;
        final int[] expandKey = new int[bytesExpand];
        final 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;
    }
    final int[] counterBlock = new int[16];
    final long nonce = 1543699; // System.currentTimeMillis();
    final long nonceMs = nonce % 1000;
    final long nonceSec = (long) Math.floor(nonce / 1000);
    final long nonceRnd = 27096; // (long)Math.floor(Math.random() * 0xffff);
    for (int i = 0; i < 2; i++) {
        counterBlock[i] = (int) ((nonceMs >>> i * 8) & 0xff);
    }
    for (int i = 0; i < 2; i++) {
        counterBlock[i + 2] = (int) ((nonceRnd >>> i * 8) & 0xff);
    }
    for (int i = 0; i < 4; i++) {
        counterBlock[i + 4] = (int) ((nonceSec >>> i * 8) & 0xff);
    }
    final StringBuilder ctrTxt = new StringBuilder();
    for (int i = 0; i < 8; i++) {
        ctrTxt.append((char) counterBlock[i]);
    }
    // generate key schedule
    final int[][] keySchedule = keyExpansion(key);
    // separate ciphertext into blocks (skipping past initial 8 bytes)
    final int nBlocks = (int) Math.ceil((plaintext.length()) / (float) blockSize);
    final String[] ciphertxt = new String[nBlocks];
    final byte[][] bytearray = new byte[nBlocks + 1][];
    bytearray[0] = ctrTxt.toString().getBytes();
    for (int b = 0; b < nBlocks; b++) {
        // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
        // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
        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 / 0x10000000 >>> c * 8; /* 0x100000000 */
        }
        final int[] cipherCntr = cipher(counterBlock, keySchedule);
        // block size is reduced on final block
        final int blockLength = (b < nBlocks - 1) ? blockSize : (plaintext.length() - 1) % blockSize + 1;
        final byte[] cipherByte = new byte[blockLength];
        for (int i = 0; i < blockLength; i++) { // -- xor plaintext with ciphered counter byte-by-byte --
            cipherByte[i] = (byte) (cipherCntr[i] ^ ord(substr(plaintext, b * blockSize + i, 1)));
        }
        ciphertxt[b] = new String(cipherByte);
        bytearray[b + 1] = cipherByte;
    }
    int bigArraySize = 0;
    for (final byte[] bytes : bytearray) {
        bigArraySize += bytes.length;
    }
    final byte[] bigArray = new byte[bigArraySize];
    copySmallArraysToBigArray(bytearray, bigArray);
    return Base64.getEncoder().withoutPadding().encodeToString(bigArray);
    // return new String(bigArray, StandardCharsets.UTF_8);
}

private static void copySmallArraysToBigArray(byte[][] smallArrays, byte[] bigArray) {
    int currentOffset = 0;
    for (final byte[] currentArray : smallArrays) {
        System.arraycopy(currentArray, 0, bigArray, currentOffset, currentArray.length);
        currentOffset += currentArray.length;
    }
}

public static int ord(String s) {
    // return s.length() > 0 ? (s.getBytes(StandardCharsets.UTF_8)[0] & 0xff) : 0;
    return s.length() > 0 ? (s.getBytes()[0] & 0xff) : 0;
}

public static int ord(char c) {
    return c < 0x80 ? c : ord(Character.toString(c));
}

private static String substr(String string, int from, int to) {
    final String substring = string.substring(string.length() - Math.abs(from));
    if (from < 0 && to < 0) {
        if (Math.abs(from) > Math.abs(to)) {
            return substring.substring(substring.length() - Math.abs(to));
        }
        else {
            return "";
        }
    }
    else if (from >= 0 && to < 0) {
        final String s = string.substring(from);
        if (Math.abs(to) >= s.length()) {
            return "";
        }
        else {
            return s.substring(0, s.length() - Math.abs(to));
        }
    }
    else if (from < 0) {
        if (to >= substring.length()) {
            return substring;
        }
        return substring.substring(0, to);
    }
    else {
        final String s = string.substring(Math.abs(from));
        if (to >= s.length()) {
            return s;
        }
        else {
            return s.substring(0, Math.abs(to));
        }
    }
}

/**
 * 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
 */
private static String decrypt(String ciphertext, String password) throws Exception {
    String plaintext = "";
    // try {
    final int blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES

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

    // use AES to encrypt password (mirroring encrypt routine)

    final int nBytes = 256 / 8; // no bytes in key
    final 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
    final int bytesExpand = nBytes - 16;

    final int keyOriginalLength = key.length;
    final int[] expandKey = new int[bytesExpand];
    final 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
    final int[] counterBlock = new int[16];
    final String ctrTxt = ciphertext.substring(0, 8);
    for (int i = 0; i < 8; i++) {
        counterBlock[i] = ctrTxt.charAt(i);
    }

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

    // separate ciphertext into blocks (skipping past initial 8 bytes)
    final int nBlocks = (int) Math.ceil((ciphertext.length() - 8) / (float) blockSize);
    final String[] cipherArr = new String[nBlocks];
    for (int b = 0; b < nBlocks; b++) {
        final int start = 8 + b * blockSize;
        final 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
    final 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;
        }

        final int[] cipherCntr = cipher(counterBlock, keySchedule); // encrypt counter block
        final 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) {
    final StringBuilder dest = new StringBuilder();
    for (final Object o : source) {
        dest.append((String) o);
    }
    return dest.toString();
}

private static String UTF8Decode(String s) throws Exception {

    final 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, StandardCharsets.UTF_8);
}

private static String base64Decoder(String data) throws Exception {

    final byte[] b = Base64.getDecoder().decode(data.getBytes(StandardCharsets.ISO_8859_1));
    // byte[] b = DatatypeConverter.parseBase64Binary(data);
    return new String(b, StandardCharsets.ISO_8859_1);
}

private static String UTF8Encode(String s) throws Exception {

    return new String(s.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
}
}

Expected Output (It runs correctly with cmd)

enter image description here

Actual output on IDEs (Intellij,Eclipse)

Max Key Size for AES : 2147483647
Encrypted string=wrsCw5hpBwYAAGNJ2u4oXMphxxzlyt+hHAm5tVc

Decrypted string=��\��ʶ����r?3�cd�^

JDK that I'm using

java version "11.0.5" 2019-10-15 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.5+10-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.5+10-LTS, mixed mode)
java
encryption
ide
aes
encryption-symmetric
asked on Stack Overflow Jun 9, 2020 by Cataclysm • edited Jun 9, 2020 by Cataclysm

2 Answers

2

The place where the error is taking place is in the encrypt-method where you generate the initialization vector (necessary for AES CTR mode). The iv is placed at the beginning of the encrypted string/byte array in bytearray[0]. Just change the line from

bytearray[0] = ctrTxt.toString().getBytes();

to

bytearray[0] = ctrTxt.toString().getBytes(StandardCharsets.ISO_8859_1);

and your program is working within your IDE or on command line.

answered on Stack Overflow Jun 9, 2020 by Michael Fehr
0

Check the encoding your files (Status Bar) and the project (Settings | Editor | File Encodings) have.

Try adding -Dfile.encoding=UTF-8 into Help | Edit Custom VM Options file and restart IDE.

Also check that the console has Font set (Settings (Preferences on macOS) | Editor | Color Scheme | Console Font) which is capable to display all the glyphs.

answered on Stack Overflow Jun 9, 2020 by Andrey

User contributions licensed under CC BY-SA 3.0