我写了一个加密&基于Serpent算法的Java解密程序,工作正常。但我想知道在数据(6字节)之前添加固定前缀是否安全。我想使用这个已知的字符串来比较加密前的字符串和解密后的字符串。任何人都可以解释我是否存在严重风险?
由于
例如:
public static final String INITIAL_FLAG = "qwerty";
crypto_serpent接口:
public String get_api_version();
//byte - byte
public byte[] encrypt(byte[] data, String passwd) throws InvalidKeyException;
public byte[] decrypt(byte[] data, String passwd) throws InvalidKeyException;
//file - byte
public byte[] encrypt(File file, String passwd) throws InvalidKeyException;
public byte[] decrypt(File file, String passwd) throws InvalidKeyException;
//file - file
public boolean encrypt(File file_in, File file_out, String passwd) throws InvalidKeyException;
public boolean decrypt(File file_in, File file_out, String passwd) throws InvalidKeyException;
//byte - file
public boolean decrypt(byte[] data, File file_out, String passwd) throws InvalidKeyException;
public boolean encrypt(byte[] data, File file_out, String passwd) throws InvalidKeyException;
public int get_block_size();
Serpent实施:
import gnu.crypto.cipher.Serpent;
import static busslina.crypto_serpent.Utils.*;
import java.io.File;
import java.security.InvalidKeyException;
import java.util.Iterator;
public class API_impl implements API
{
private static final String API_VERSION = "1.1";
/**
* Current api interfaz/implementation version
* @return
*/
@Override
public String get_api_version()
{
return API_VERSION;
}
public static final String INITIAL_FLAG = "QWERTY";
public static final int KEY_SIZE = 16;
public static final int BLOCK_SIZE = 16;
/**
* TESTED. WORKS FINE
* @param data
* @param passwd
* @return
* @throws InvalidKeyException
*/
@Override
public byte[] encrypt(byte[] data, String passwd) throws InvalidKeyException
{
if(passwd.length() > KEY_SIZE)
throw new InvalidKeyException("Password too long");
Serpent s = new Serpent();
//passwd (we need key to encrypt)
byte[] passwd_byte = Utils.string_to_byt_array(passwd);
byte[] expanded_passwd_byte = Utils.expand_byt_array(passwd_byte, KEY_SIZE);
Object key = s.makeKey(expanded_passwd_byte, BLOCK_SIZE);
//data (flag + modulus + original data + alignment empty bytes (zeros))
byte[] flag = string_to_byt_array(INITIAL_FLAG);
Integer modulus = (flag.length + 1 + data.length) % BLOCK_SIZE;
Byte mod = modulus.byteValue();
byte[] data_flag_extended = concat_array(flag, mod);
data_flag_extended = concat_array(data_flag_extended, data);
if(modulus != 0)
data_flag_extended = fill_with_zeros(data_flag_extended, data_flag_extended.length + BLOCK_SIZE - modulus);
//data encrypt (block size) loop
int pos = 0;
byte[] out = new byte[data_flag_extended.length];
while(true)
{
s.encrypt(data_flag_extended, pos, out, pos, key, BLOCK_SIZE);
pos += BLOCK_SIZE;
if(pos == data_flag_extended.length)
return out;
}
}
/**
* TESTED. WORKS FINE
* @param data
* @param passwd
* @return NULL on password fail
* @throws java.security.InvalidKeyException
*/
@Override
public byte[] decrypt(byte[] data, String passwd) throws InvalidKeyException
{
if(passwd.length() > KEY_SIZE)
throw new InvalidKeyException("Password too long");
Serpent s = new Serpent();
//passwd (we need key to decrypt)
byte[] passwd_byte = Utils.string_to_byt_array(passwd);
byte[] expanded_passwd_byte = Utils.expand_byt_array(passwd_byte, KEY_SIZE);
Object key = s.makeKey(expanded_passwd_byte, BLOCK_SIZE);
//data decrypt
byte[] out = new byte[data.length];
int pos = 0;
while(true)
{
s.decrypt(data, pos, out, pos, key, BLOCK_SIZE);
pos += BLOCK_SIZE;
if(pos == data.length)
break;
}
//flag check & metedata delete (flag and modulus)
int flag_size = INITIAL_FLAG.length();
String flag = new String(subarray(out, 0, flag_size));
if(!flag.equals(INITIAL_FLAG))
return null;
//flag check passed
//now, deletion of metadata
int modulus = out[flag_size];
byte[] data_no_metadata = subarray(out, flag_size + 1, out.length - flag_size - 1);
//now, deletion of zeroes added in order to align with BLOCK_SIZE
if(modulus == 0)
return data_no_metadata;
return subarray(data_no_metadata, 0, data_no_metadata.length - BLOCK_SIZE + modulus);
}
static void show_cipher_info()
{
Serpent s = new Serpent();
w("block sizes:");
Iterator<Integer> it = s.blockSizes();
while(it.hasNext())
w(it.next());
w("key sizes:");
it = s.keySizes();
while(it.hasNext())
w(it.next());
/*
block sizes:
16
key sizes:
16
24
32
*/
}
@Override
public byte[] encrypt(File file, String passwd) throws InvalidKeyException {
boolean check_exists = true;
boolean exit_on_fail = true;
check_file(file, check_exists, exit_on_fail);
byte[] data = read_file(file);
return encrypt(data, passwd);
}
@Override
public byte[] decrypt(File file, String passwd) throws InvalidKeyException {
boolean check_exists = true;
boolean exit_on_fail = true;
check_file(file, check_exists, exit_on_fail);
byte[] data = read_file(file);
return decrypt(data, passwd);
}
@Override
public boolean encrypt(File file_in, File file_out, String passwd) throws InvalidKeyException {
boolean check_exists = true;
boolean exit_on_fail = true;
check_file(file_in, check_exists, exit_on_fail);
check_exists = false;
exit_on_fail = true;
check_file(file_out, check_exists, exit_on_fail);
byte[] data = read_file(file_in);
write_file(file_out, encrypt(data, passwd));
return true;
}
@Override
public boolean decrypt(File file_in, File file_out, String passwd) throws InvalidKeyException {
boolean check_exists = true;
boolean exit_on_fail = true;
check_file(file_in, check_exists, exit_on_fail);
check_exists = false;
exit_on_fail = true;
check_file(file_out, check_exists, exit_on_fail);
byte[] data = read_file(file_in);
byte[] decrypt = decrypt(data, passwd);
if(decrypt == null)
return false;
write_file(file_out, decrypt);
return true;
}
@Override
public boolean decrypt(byte[] data, File file_out, String passwd) throws InvalidKeyException {
boolean check_exists = false;
boolean exit_on_fail = true;
check_file(file_out, check_exists, exit_on_fail);
byte[] decrypt = decrypt(data, passwd);
if(decrypt == null)
return false;
write_file(file_out, decrypt);
return true;
}
@Override
public boolean encrypt(byte[] data, File file_out, String passwd) throws InvalidKeyException {
boolean check_exists = false;
boolean exit_on_fail = true;
check_file(file_out, check_exists, exit_on_fail);
write_file(file_out, encrypt(data, passwd));
return true;
}
@Override
public int get_block_size() {
return BLOCK_SIZE;
}
}
Utils.java:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Utils {
public static void w(Object msg)
{
System.out.println(msg);
}
public static byte[] string_to_byt_array(String txt)
{
char[] toCharArray = txt.toCharArray();
byte[] ret = new byte[toCharArray.length];
for(int i = 0; i < toCharArray.length; i++)
ret[i] = (byte)toCharArray[i];
return ret;
}
public static Byte[] string_to_byte_array(String txt)
{
char[] toCharArray = txt.toCharArray();
Byte[] ret = new Byte[toCharArray.length];
for(int i = 0; i < toCharArray.length; i++)
ret[i] = (byte)toCharArray[i];
return ret;
}
public static byte[] expand_byt_array(byte[] array, int size)
{
if(array.length == size)
return array;
else if(array.length > size)
return null;
byte[] ret = new byte[size];
System.arraycopy(array, 0, ret, 0, array.length);
return ret;
}
public static byte[] concat_array(byte[] array_a, byte[] array_b)
{
return concat_array(array_a, array_b, -1, -1, -1, -1);
}
public static byte[] concat_array(byte[] array_a, byte[] array_b,
int init_a, int length_a, int init_b, int length_b)
{
if(init_a == -1)
init_a = 0;
if(length_a == -1)
length_a = array_a.length;
if(init_b == -1)
init_b = 0;
if(length_b == -1)
length_b = array_b.length;
byte[] ret = new byte[length_a - init_a + length_b - init_b];
int cont = 0;
//array a
for(int i = init_a; i < length_a + init_a; cont++, i++)
ret[cont] = array_a[i];
//array b
for(int i = init_b; i < length_b + init_b; cont++, i++)
ret[cont] = array_b[i];
return ret;
}
public static byte[] concat_array(byte[] array_a, byte b)
{
byte[] array_b = new byte[]{b};
return concat_array(array_a, array_b);
}
public static <T> T[] concat_array(T[] array_a, T[] array_b)
{
T[] ret = (T[])new Object[array_a.length + array_b.length];
//array a
System.arraycopy(array_a, 0, ret, 0, array_a.length);
//array b
for(int index_x = 0, index_y = array_a.length; index_x < array_b.length; index_x++, index_y++)
ret[index_y] = array_b[index_x];
return ret;
}
public static <T> T[] concat_array(T[] array_a, T b)
{
T[] array_b = (T[])new Object[]{b};
return concat_array(array_a, array_b);
}
public static Byte[] array_byte_conversion(byte[] array)
{
Byte[] ret = new Byte[array.length];
for(int i = 0; i < array.length; i++)
ret[i] = array[i];
return ret;
}
public static byte[] array_byt_conversion(Byte[] array)
{
byte[] ret = new byte[array.length];
for(int i = 0; i < array.length; i++)
ret[i] = array[i];
return ret;
}
/**
* NOT TESTED
* @param <T>
* @param array
* @param pos
* @param length
* @return
*/
public static <T> T[] subarray(T[] array, int pos, int length)
{
if(pos + length > array.length)
length = pos + length - array.length;
T[] ret = (T[])new Object[length];
for(int i = 0; i < length; i++)
ret[i] = array[i + pos];
return ret;
}
/**
* NOT TESTED
* @param array
* @param pos
* @param length
* @return
*/
public static byte[] subarray(byte[] array, int pos, int length)
{
//check overflow on right
if(pos + length > array.length)
length = pos + length - array.length;
byte[] ret = new byte[length];
for(int i = 0; i < length; i++)
ret[i] = array[i + pos];
return ret;
}
public static byte[] fill_with_zeros(byte[] array, int desired_size)
{
if(array.length >= desired_size)
return array;
byte[] ret = new byte[desired_size];
System.arraycopy(array, 0, ret, 0, array.length);
return ret;
}
public static boolean compare_byt_array(byte[] a, byte[] b)
{
if(a.length != b.length)
return false;
for(int i = 0; i < a.length; i++)
{
if(a[i] != b[i])
return false;
}
return true;
}
public static boolean check_file(File file, boolean check_exists, boolean exit_on_fail)
{
boolean ret = file.exists();
if(ret != check_exists && exit_on_fail)
{
if(ret)
w("Error. File already exists");
else
w("Error. File not found");
System.exit(0);
}
if(check_exists)
return ret;
else
return !ret;
}
public static byte[] read_file(File file)
{
byte[] ret = new byte[0];
try
{
FileInputStream fis = new FileInputStream(file);
byte[] read = new byte[1024];
int cont;
while((cont = fis.read(read)) != -1)
ret = concat_array(ret, read, -1, -1, 0, cont);
}
catch(FileNotFoundException e)
{
return null;
}
catch(IOException e)
{
return null;
}
return ret;
}
public static boolean write_file(File file, byte[] data)
{
try
{
FileOutputStream fos = new FileOutputStream(file);
fos.write(data);
return true;
}
catch(IOException e)
{
return false;
}
}
}
答案 0 :(得分:3)
如果我能正确理解这一点,那么您的关注点是“是否已经修复了&#34;结构可能会增加known-plaintext attack的风险。已知的明文攻击是指攻击者可以访问&#34;明文&#34; (即未加密的消息)及其伴随的#34;密文&#34; (即加密文本)。对于一些较旧的算法,可以基于这些已知明文中的一个或几个来发现密钥。这使得解密和读取使用相同密钥加密的任何其他消息成为可能。
事实上,第二次世界大战中的Enigma machine成为攻击的受害者,就像你所描述的那样 - 报告(例如天气预报)倾向于遵循固定的,可预测的结构,这使得盟军猜测他们的内容,以获得已知的明文。然后,他们将使用已知的明文来恢复当天的密钥并使用密钥来解密其他消息。在这方面,非洲的一名官员特别有帮助,并且会回复报告&#34;每天大约在同一时间。
顺便提一下,出于显而易见的原因,现代算法被设计为对已知明文攻击是安全的。您通常需要大量已知的明文才能成功恢复一个密钥。
话虽如此,现代算法不易受此攻击。即使攻击者确实发现了您的消息结构,并且能够猜测&#34;一些已知的明文,攻击者仍然需要大量的明文才能恢复密钥。
加密算法具有的另一个重要特性:明文中的非常小的差异将倾向于在密文中产生很大的差异。例如,即使使用较旧的DES算法,更改单个位也可能导致密文中最多31位的更改。这就是所谓的avalanche effect。这将使得获得有用信息变得更加困难,因为很难确定密文的哪一部分与#34;相对应。对于明文的哪一部分 - 它根本不可能找出为什么特定位被设置为没有访问原始密钥的方式。有无保证密文的第1位被设置为由明文第1位的值引起的方式 - 例如 - 对于所有攻击者都知道,它可能已经由于第10位的变化而被更改。
如@ScottNewson所述,this answer也可以为您提供帮助。
考虑以下明文消息:
1001010100110101010101110
对应于以下密文:
0110000111100110101000101
现在让我们假设我在明文中将最后一位更改为1:
1001010100110101010101111
这可能会改变密文中的多个位,这些位可能包括也可能不包括最后一位,因此没有理由相信它会导致像
1001010100110101010101110
事实上,它极不可能会导致这种变化。
TL; DR 了解邮件的结构对潜在的攻击者不会特别有用。