Encrypted mailbox Demo based on AES and RSA

Project Introduction

Network security end-of-term homework: Through the symmetric encryption and asymmetric encryption algorithms explained in class, realize an email client that can encrypt and decrypt.

Note: This article does not introduce the algorithm, but only the main implementation process.


Show results

generate key

encrypted mail

decrypt


overall design

Implementing an encrypted email client in Java, as it is a network security operation, mainly lies in the writing of encryption and decryption algorithms. Here I use a randomly generated AES128 key to encrypt the ciphertext, then use other users’ public keys to encrypt the AES128 key using RSA, and finally encapsulate it into an EncryptMessage object to send the key and ciphertext to other users.

The flow chart is as follows:

Encrypted mail algorithm design

encryption design
  1. Use a randomly generated string as the key of AES128 to encrypt the ciphertext.

  2. Then encrypt the AES128 key with RSA using the other user's public key.

  3. Finally, it is encapsulated into an EncryptMessage object, and the key and ciphertext are sent to other users.

Other designs
  1. Character set encoding: Unicodee supports all character encodings.

  2. Output format: Base64 encoding.

  3. EncryptMessage object:

    • Key: RSA encrypted AES key.
    • Ciphertext: AES encrypted ciphertext.

Encrypted mail algorithm part code

	/**
     * Get the public key and private key
     *
     * @return
     */
    public static RSAUtils.SecretKey createRSAKeyPair()
    {
        return RSAUtils.createKeyPair();
    }

    /**
     * encryption
     *
     * @param plaintext clear text
     * @param publicKey public key
     * @return
     */
    public static EncryptMessage encrypt(String plaintext, String publicKey) throws UnsupportedEncodingException
    {
        // base64 decode public key
        publicKey = new String(Base64.getDecoder().decode(publicKey));
        // Generate random AES128 key
        String secretKey = getRandomString(16);
        // AES128 encrypted plaintext
        String cipherText = AESUtils.encrypt(plaintext, secretKey);
        // RSA encrypted AES128 key
        String key = encryptSecretKey(secretKey, publicKey);
        // base64 encoded email ciphertext and key
        cipherText = new String(Base64.getEncoder().encode(cipherText.getBytes()));
        key = new String(Base64.getEncoder().encode(key.getBytes()));
        return new EncryptMessage(key, cipherText);
    }

    /**
     * decrypt
     *
     * @param encryptMessage encrypted mail
     * @param privateKey private key
     * @return
     */
    public static String decrypt(EncryptMessage encryptMessage, String privateKey) throws UnsupportedEncodingException
    {
        // base64 decode private key
        privateKey = new String(Base64.getDecoder().decode(privateKey));
        // Get email ciphertext and key
        String cipherText = encryptMessage.getCipherText();
        String key = encryptMessage.getKey();
        // base64 decode ciphertext and key
        cipherText = new String(Base64.getDecoder().decode(cipherText));
        key = new String(Base64.getDecoder().decode(key));
        // AES128 key to decrypt ciphertext
        String secretKey = decryptSecretKey(key, privateKey);
        return AESUtils.decrypt(cipherText, secretKey);
    }

AES algorithm

  • The encoding method is Unicode, which supports all character encodings;
  • Key: AES128;
  • encryption mode: ECB;
  • Filling method: ZerosPadding.

Implementation steps

encryption steps
  1. Plaintext and keys are converted to byte arrays in Unicode encoding format;

  2. Fill the plaintext to be a multiple of 16 bytes, and even if it is a multiple of 16 bytes, it must also be filled;

  3. Plaintext, key array to byte matrix;

  4. block encryption

    • Generate extended key

    • initial change

    • 9 rounds

    • last round

  5. Convert ciphertext byte matrix to ciphertext byte array;

  6. Convert ciphertext byte array to ciphertext byte string;

  7. Ciphertext splicing.

Decryption steps
  1. The key is converted into a byte array in Unicode encoding format;
  2. Convert ciphertext hexadecimal string to byte array;
  3. Convert ciphertext and key array to byte matrix;
  4. block decryption
    • Generate an extended key;
    • initial change;
    • 9 cycles;
    • final round
  5. Plaintext byte matrix to plaintext byte array;
  6. The plaintext byte array is converted to a string according to the Unicode encoding method;
  7. Plaintext splicing.

key code

    /**
     * column-mixed field multiplication
     *
     * @param fixMatrix fixed matrix
     * @param content 
     * @return
     */
    private static byte mixMultiply(byte fixMatrix, byte content)
    {
        byte mulContent = 0;
        if (fixMatrix == 0x01)
        {
            return content;
        }
        else if (fixMatrix == 0x02)
        {
            mulContent = GF(content, fixMatrix);
        }
        else if (fixMatrix == 0x03)
        {
            mulContent = (byte) (GF(content, (byte) 0x02) ^ content);
        }
        else if (fixMatrix == 0x09)
        {
            mulContent = (byte) (GF(GF(GF(content, (byte) 0x02), (byte) 0x02), (byte) 0x02) ^ content);
        }
        else if (fixMatrix == 0x0B)
        {
            mulContent = (byte) ((GF(content, (byte) 0x02) ^ GF(GF(GF(content, (byte) 0x02), (byte) 0x02), (byte) 0x02)) ^ content);
        }
        else if (fixMatrix == 0x0D)
        {
            mulContent = (byte) ((GF(GF(content, (byte) 0x02), (byte) 0x02) ^ GF(GF(GF(content, (byte) 0x02), (byte) 0x02), (byte) 0x02)) ^ content);
        }
        else if (fixMatrix == 0x0E)
        {
            mulContent = (byte) (GF(content, (byte) 0x02) ^ GF(GF(content, (byte) 0x02), (byte) 0x02) ^ GF(GF(GF(content, (byte) 0x02), (byte) 0x02), (byte) 0x02));
        }

        return mulContent;
    }

    /**
     * multiplication operation
     * 
     * @param content
     * @param fixMatrix fixed matrix
     * @return
     */
    private static byte GF(byte content, byte fixMatrix)
    {
        byte mulContent = 0;

        if ((content & 0x80) == 0x80)
        {
            mulContent = (byte) ((byte) (content << 1) ^ 0x1B);
        }
        else
        {
            mulContent = (byte) (content << 1);
        }

        return mulContent;
    }

RSA algorithm

  • The main implementation of the RSA algorithm in this article is taken from the CSDN community foDask Jhonson RSA algorithm principle and implementation (Java) .

  • The public key PublicKey(e, n) and private key (d, n) generated by RSA are of type BigInteger;

  • The format of the public key and the secret key is two very large integers separated by ".", and finally encoded with Base64.

  • Prime number length: 1024 bit;

  • Accuracy of prime numbers 1-(2 ^ (-accuracy)): 128;

  • The value of the public key exponent e: According to the suggestion of PKCS#1, the public key exponent e can choose a smaller prime number 3 or 65537 (=2^16+1). Take 65537 here.

Implementation steps

  1. Choose a pair of prime numbers p, q that are not equal and large enough;
  2. Compute the product n of p, q;
  3. Calculate the Euler function of n φ(n) = (p - 1) * (q - 1);
  4. Choose an integer e that is relatively prime to φ(n) (1 < e < φ(n));
  5. Compute the inverse element d of e with respect to φ(n);
  6. The calculated d cannot be negative, if it is negative, d = d + φ(n). make d positive;
  7. Returns the computed key.

key code

	/**
     * Extended Euclidean method to find the inverse element -- find the inverse element d of e
     * d * e - y * φ(n) = 1 (e and φ(n) coprime)
     * d * e - y * φ(n) = gcd(e, φ(n))
     * d * e - y * φ(n) = gcd(φ(n), e % φ(n))
     * d * e - y * φ(n) = φ(n) * d` - y` * [e % φ(n)] (If the current is the last formula, then e % φ(n) = 0, start to bring back the value)
     * d * e - y * φ(n) = φ(n) * d` - y` * [e - e / φ(n) * φ(n)]
     * d * e - y * φ(n) = - y` * e + [d` + e / φ(n) * y`] * φ(n)
     * d = -y` and y = d` - e / φ(n) * y`
     * Since d cannot be negative, we need to use d=d+φ(n) to make it a positive number
     *
     * @param e e
     * @param PHI_n φ(n)
     * @return bigIntegers(least common factor, d, y)
     */
    private static BigInteger[] exGCD(BigInteger e, BigInteger PHI_n)
    {
        // φ(n) = 0 -> (1, 1, 0) such that d * e - y * φ(n) = 1
        if (PHI_n.signum() == 0)
        {
            return new BigInteger[]{e, new BigInteger("1"), new BigInteger("0")};
        }
        else
        {
            BigInteger[] bigIntegers = exGCD(PHI_n, e.mod(PHI_n));
            // y = d` - e / φ(n) * y`
            BigInteger y = bigIntegers[1].subtract(e.divide(PHI_n).multiply(bigIntegers[2]));
            return new BigInteger[]{bigIntegers[0], bigIntegers[2], y};
        }
    }


Finally: I hope this article can help you solve your problem. If there is anything you don’t understand or is incorrect in the article, please explain it in the comment area below!

references:

  • Dask Jhonson. Principle and Implementation of RSA Algorithm (Java). [EB/OL]. (2020-05-02). [2022-12-04]. https://blog.csdn.net/qq_41115702/article/details/ 105884973

blog address

Tags: Java Cyber Security

Posted by FatalError on Tue, 24 Jan 2023 22:31:03 +1030