redmanmale

if-goto

Защита паролей в конфигах с помощью AES

[ csharp | java | windows | aes ]

Abstract

Шифруем на C#, расшифровываем на Java. Потому что надо.

Introduction

В продолжение предыдущей темы.

Выяснилось, что DPAPI работает очень интересно. Если пользователь доменный, то можно зашифровать на одной машине, а расшифровать на другой. Но только если вылогиниться со всех других машин, а это максимально не наш сценарий.

В результате было решено переделать шифрование паролей на AES, с хранением ключа в файле, с ограничением к нему доступа на уровне файловой системы.

Methods

Реализации AES есть на популярных языках, тем более на C#/Java. Однако в процессе пришлось немного поприседать.

Код довольно примитивен, из интересного. Для работы AES нужны два компонента: ключ и вектор инициализации.
Ключ всегда одинаковый, лежит в файле.
Вектор инициализации (IV) каждый раз новый, но для расшифровки нужен именно тот, который участвовал в шифровании. Решил хранить его вместе с шифротекстом, в начале.

Шифруем:

private static string Encrypt(string plain, string keyBase64)
{
    using (var aes = new AesCryptoServiceProvider
    {
        Key = Convert.FromBase64String(keyBase64),
        Mode = CipherMode.CBC
    })
    {
        aes.GenerateIV();
        var iv = aes.IV;
        using (var encryptor = aes.CreateEncryptor(aes.Key, iv))
        using (var cipherStream = new MemoryStream())
        {
            using (var tCryptoStream = new CryptoStream(cipherStream, encryptor, CryptoStreamMode.Write))
            using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
            {
                cipherStream.Write(iv, 0, iv.Length);
                tBinaryWriter.Write(Encoding.UTF8.GetBytes(plain));
                tCryptoStream.FlushFinalBlock();
            }

            return Convert.ToBase64String(cipherStream.ToArray());
        }
    }
}

Дешифруем

private static @NotNull String decrypt( @NotNull String encryptedDataBase64, @NotNull String keyBase64 ) throws ConfigurationException
{
    int ivSize = 16;
    try
    {
        byte[] key = Base64.getDecoder().decode( keyBase64 );
        byte[] encryptedDataBytes = Base64.getDecoder().decode( encryptedDataBase64 );
        byte[] actualDataBytes = Arrays.copyOfRange( encryptedDataBytes, ivSize, encryptedDataBytes.length );
        byte[] ivBytes = Arrays.copyOfRange( encryptedDataBytes, 0, ivSize );

        Key javaKey = new SecretKeySpec( key, "AES" );
        AlgorithmParameterSpec iv = new IvParameterSpec( ivBytes );

        Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5PADDING" );
        cipher.init( Cipher.DECRYPT_MODE, javaKey, iv );

        return new String( cipher.doFinal( actualDataBytes ), StandardCharsets.UTF_8 );
    }
    catch( Exception e )
    {
        throw new ConfigurationException( Errors.PasswordDecryptionFailed, e, e.getMessage() );
    }
}

Results

Код на C# был вставлен в Powershell-скрипт вместо работы с DPAPI, код на Java отправился в продукт.