navneet83 / Cross-platform-AES-encryption

Basic cross platform AES encryption
Apache License 2.0
320 stars 152 forks source link

Refactor - c#, android and web #66

Closed MihailsKuzmins closed 4 years ago

MihailsKuzmins commented 4 years ago

.NET changes (c#):

public class EncryptionService2
{
    private const int
        KeySize = 256,
        BlockSize = 128,
        KeyLength = 32,
        IvLength = 16;

    private static Encoding Encoding => Encoding.UTF8;

    public string Encrypt(string inputText, string encryptionKey, string initVector)
    {
        using var rijndaelAlgorithm = GetRijndaelAlgorithm(encryptionKey, initVector);
        using var encryptor = rijndaelAlgorithm.CreateEncryptor();

        var inputBytes = Encoding.GetBytes(inputText);

        return encryptor
            .TransformFinalBlock(inputBytes, 0, inputBytes.Length)
            .Run(Convert.ToBase64String);
    }

    public string Decrypt(string inputText, string encryptionKey, string initVector)
    {
        using var rijndaelAlgorithm = GetRijndaelAlgorithm(encryptionKey, initVector);
        using var decryptor = rijndaelAlgorithm.CreateDecryptor();

        var inputBytes = Convert.FromBase64String(inputText);

        return decryptor
            .TransformFinalBlock(inputBytes, 0, inputBytes.Length)
            .Run(Encoding.GetString);
    }

    private static RijndaelManaged GetRijndaelAlgorithm(string encryptionKey, string initVector)
    {
        var encryptionKeyBytes = Encoding.GetBytes(encryptionKey);
        var initVectorBytes = Encoding.GetBytes(initVector);

        var encryptionKeyArray = new byte[KeyLength];
        var initialVectorArray = new byte[IvLength];

        CopyBytes(encryptionKeyBytes, encryptionKeyArray);
        CopyBytes(initVectorBytes, initialVectorArray);

        return new RijndaelManaged
        {
            Mode = CipherMode.CBC,
            Padding = PaddingMode.PKCS7,
            KeySize = KeySize,
            BlockSize = BlockSize,
            Key = encryptionKeyArray,
            IV = initialVectorArray
        };
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static void CopyBytes(byte[] source, byte[] destination) =>
        Array.Copy(source, destination, Math.Min(source.Length, destination.Length));
}

Android changes (Kotlin):

class EncryptionService {
    fun encrypt(inputText: String, encryptionKey: String, initVector: String): String =
        getCipher(Cipher.ENCRYPT_MODE, encryptionKey, initVector)
            .doFinal(inputText.toByteArray(charset))
            .run { Base64.encodeToString(this, Base64.DEFAULT) }

    fun decrypt(inputText: String, encryptionKey: String, initVector: String): String {
        val decodedValue = inputText.toByteArray()
            .run { Base64.decode(this, Base64.DEFAULT) }

        return getCipher(Cipher.DECRYPT_MODE, encryptionKey, initVector)
            .doFinal(decodedValue)
            .run<ByteArray, String>(::String)
    }

    companion object {
        private const val keyLength = 32
        private const val ivLength = 16
        private val charset = Charsets.UTF_8

        private fun getCipher(mode: Int, encryptionKey: String, initVector: String): Cipher {
            val encryptionKeyBytes = encryptionKey.toByteArray(charset)
            val initVectorBytes = initVector.toByteArray(charset)

            val secretKeySpec = ByteArray(keyLength)
                .also { copyBytes(encryptionKeyBytes, it) }
                .run { SecretKeySpec(this, "AES") }

            val ivParameterSpec = ByteArray(ivLength)
                .also { copyBytes(initVectorBytes, it) }
                .run(::IvParameterSpec)

            return Cipher.getInstance("AES/CBC/PKCS5Padding")
                .apply { init(mode, secretKeySpec, ivParameterSpec) }
        }

        private fun copyBytes(source: ByteArray, destination: ByteArray) {
            System.arraycopy(source, 0, destination, 0, minOf(source.size, destination.size))
        }
    }
}

Web changes (TypeScript)

export default class EncryptionService {
    private static readonly KeySize = 32
    private static readonly IvSize = 16
    private readonly mAlgorithm = 'AES-256-CBC'

    encrypt(plainText: string, encryptionKey: string, initVector: string) {
        const [encryptionKeyArray, initVectorArray] = EncryptionService.getByteArrays(encryptionKey, initVector)

        const encryptor = Crypto.createCipheriv(this.mAlgorithm, encryptionKeyArray, initVectorArray)
        encryptor.setEncoding('base64')
        encryptor.write(plainText)
        encryptor.end()

        return encryptor.read()
    }

    decrypt(encryptedText: string, encryptionKey: string, initVector: string) {
        const [encryptionKeyArray, initVectorArray] = EncryptionService.getByteArrays(encryptionKey, initVector)

        const decryptor = Crypto.createDecipheriv(this.mAlgorithm, encryptionKeyArray, initVectorArray)

        return decryptor
            .update(encryptedText, 'base64', 'utf8')
            .concat(decryptor.final('utf8'))
    }

    private static getByteArrays(encryptionKey: string, initVector: string) {
        const encryptionKeyArray = new Uint8Array(EncryptionService.KeySize),
            initVectorArray = new Uint8Array(EncryptionService.IvSize)

        const textEncoder = new TextEncoder()
        textEncoder.encodeInto(encryptionKey, encryptionKeyArray)
        textEncoder.encodeInto(initVector, initVectorArray)

        return [encryptionKeyArray, initVectorArray] as const
    }
}