-->

how to match IV in AESCrypt with JAVA and RUBY

2020-08-01 11:40发布

问题:

I put the Encrypted ID and Nickname to Database. but there is a problem when I need to get the ID and nickname in ruby because AESCrypt in ruby doesn't work.

I think the problem is IV that isn't same with ruby and java

here is I used in java code (actually in android)

public class AESCrypt {

private final Cipher cipher;
private final SecretKeySpec key;
private AlgorithmParameterSpec spec;




public AESCrypt(String password) throws Exception
{
    // hash password with SHA-256 and crop the output to 128-bit for key
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    digest.update(password.getBytes("UTF-8"));
    byte[] keyBytes = new byte[16];
    System.arraycopy(digest.digest(), 0, keyBytes, 0, keyBytes.length);

    cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    key = new SecretKeySpec(keyBytes, "AES");
    spec = getIV();
}       

public AlgorithmParameterSpec getIV()
{
    byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
    IvParameterSpec ivParameterSpec;
    ivParameterSpec = new IvParameterSpec(iv);

    return ivParameterSpec;
}

public String encrypt(String plainText) throws Exception
{
    cipher.init(Cipher.ENCRYPT_MODE, key, spec);
    byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
    String encryptedText = new String(Base64.encode(encrypted, Base64.DEFAULT), "UTF-8");

    return encryptedText;
}

public String decrypt(String cryptedText) throws Exception
{
    cipher.init(Cipher.DECRYPT_MODE, key, spec);
    byte[] bytes = Base64.decode(cryptedText, Base64.DEFAULT);
    byte[] decrypted = cipher.doFinal(bytes);
    String decryptedText = new String(decrypted, "UTF-8");

    return decryptedText;
}

}

and I need to decrypt the data in ruby so i used the code below

require 'openssl'
require 'base64'

module AESCrypt

  def self.encrypt(message, password)
    Base64.encode64(self.encrypt_data(message.to_s.strip,         
    self.key_digest(password), nil, "AES-256-CBC"))
  end

  def self.decrypt(message, password)
    base64_decoded = Base64.decode64(message.to_s.strip)
    self.decrypt_data(base64_decoded, self.key_digest(password), nil, "AES-256-CBC")
  end

  def self.key_digest(password)
    OpenSSL::Digest::SHA256.new(password).digest
  end


  def self.decrypt_data(encrypted_data, key, iv, cipher_type)
    aes = OpenSSL::Cipher::Cipher.new(cipher_type)
    aes.decrypt
    aes.key = key
    aes.iv = "0000000000000000"
    aes.update(encrypted_data) + aes.final  
  end

  def self.encrypt_data(data, key, iv, cipher_type)
    aes = OpenSSL::Cipher::Cipher.new(cipher_type)
    aes.encrypt
    aes.key = key
    aes.iv = "0000000000000000"
    aes.update(data) + aes.final      
  end
end

how to match IV in ruby to Java? and is this a IV problem?

回答1:

There are a number of problems with this code:

  1. You initialize the initialization vector to 16 bytes of 0. Unfortunately this is recommended many places on the internet but it defeats the purpose of an IV -- a random and unpredictable value to seed the Cipher Block Chain Mode of Operation. This value does not need to be kept secret (i.e. you can prepend it to the cipher text and transmit over clear channels) but it must be random and unique for every value encrypted with the same key.
    • Not only do you generate a static IV, you do it during class construction, so even if you were to modify the code in getIV() to return a random IV, it would be constant for all values encrypted by this class; again, defeating the purpose.
  2. Your key derivation function (KDF) is weak -- so weak as to be irrelevant to preventing brute force attacks. A strong solution is something like bcrypt, scrypt, or PBKDF2 with HMAC/SHA-256. I'm assuming this code is only for demo/learning.
  3. In your Ruby code, the encrypt and decrypt methods accept a parameter for iv that is unused. Instead, you assign a constant string value to the cipher IV. You should be assigning the byte values of that string value to the IV (see sample code below).
  4. It appears you are implementing "greenfield" development -- i.e. this code does not have to be backward compatible with some existing implementation, as you are writing both a Java and Ruby client. Might I suggest starting with an authenticated cipher? "Authenticated Encryption with Associated Data" (AEAD) is necessary to avoid cipher text manipulation and the decryption of unauthorized cipher texts. This can lead to data corruption or data decryption by a malicious actor through brute forcing a padding oracle. Long story short -- someone can change the information stored in your system or intercept and recover the plaintext. Consider AES/GCM (Galois/Counter Mode), or if you are required to use AES/CBC, adding an HMAC/SHA-256 construction which MACs the cipher text, not the plaintext.
  5. Cryptography is a field where learning and curiosity and exploration is very necessary and valuable. However, it is also very dangerous, as crypto is necessarily used when protecting sensitive data. It is easy to stumble on to what appears to be the right combination of magic incantations to get something working, but it is susceptible to side-channel attacks, timing attacks, key leakage, chosen plaintext or cipher text attacks, or padding oracles. If you are using this to protect data you care about (customer, user, financial, PII, etc.) DO NOT write this code yourself. The first rule of crypto is don't. roll. your own crypto. Use a trusted, verified, robust library written by experts, and update it regularly to ensure that any vulnerabilities are mitigated. BouncyCastle or KeyCzar for Java and NaCL for Ruby are well-recommended and well-tested.

Here is sample code (taken from an Apache NiFi test resource and modified for this example) which uses the same KDF you are using and encrypts and decrypts a simple message. Note the commented line which would generate a random IV and use it for a more secure operation. For more information on byte manipulation in Ruby (i.e. ("\0" * 16).unpack('c*').pack('s*')) see Byte manipulation in Ruby.

#!/usr/bin/env ruby

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'openssl'

def bin_to_hex(s)
  s.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join
end

plaintext = "This is a plaintext message."
puts "Plaintext: #{plaintext}"

cipher = OpenSSL::Cipher.new 'AES-128-CBC'
cipher.encrypt
# iv = cipher.random_iv
# This creates a string of 16 0x00 bytes
iv = ("\0" * 16)

key_len = cipher.key_len
digest = OpenSSL::Digest::SHA256.new
key = digest.digest(plaintext)[0..15]

puts ""

puts "  IV: #{bin_to_hex(iv)} #{iv.length}"
puts " Key: #{bin_to_hex(key)} #{key.length}"
cipher.key = key
cipher.iv = iv

# Now encrypt the data:

encrypted = cipher.update plaintext
encrypted << cipher.final
puts "Cipher text length: #{encrypted.length}"
puts "Cipher text: #{bin_to_hex(encrypted)}"

cipher.decrypt
cipher.iv = iv
cipher.key = key
decrypted = cipher.update encrypted
decrypted << cipher.final
puts "Plaintext length: #{decrypted.length}"
puts "Plaintext: #{decrypted}"

And the resulting output:

$ ./openssl_aes.rb
Plaintext: This is a plaintext message.

  IV: 00000000000000000000000000000000 16
 Key: c72943d27c3e5a276169c5998a779117 16
Cipher text length: 32
Cipher text: 4f6ab6c5a6c628693f77aa9e2c63b3c5366f433fcad9fbd623510c2ba517381f
Plaintext length: 28
Plaintext: This is a plaintext message.

You can now feed this data back into your Java class (note the Ruby output is hex-encoded, not Base64) to verify.