【问题标题】:Is it secure to add a constant prefix string before encrypting data?在加密数据之前添加常量前缀字符串是否安全?
【发布时间】:2017-10-25 12:07:56
【问题描述】:

我用Java写了一个基于Serpent算法的加解密程序,效果很好。但我想知道在数据(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;
    }
   }
}

【问题讨论】:

  • 那么,基本上,您发送的是一些未加密的元数据?
  • 安全在什么意义上,来自谁?你爱管闲事的 8 岁妹妹?地痞子?主要有组织犯罪集团?
  • 元数据是什么?
  • 加密时添加此标志,解密时我会查找此标志,如果我不匹配,则密码错误。我的问题是这种方法是否有风险,因为如果有人知道固定标志(源代码)并且有两个或更多加密文件,那么破解密码可能会更容易。
  • 并将标志与数据一起插入,然后一起加密

标签: java security encryption


【解决方案1】:

如果我的理解正确,您担心的是“固定”结构是否会增加known-plaintext attack 的风险。已知的明文攻击是指攻击者可以访问“明文”(即未加密的消息)及其伴随的“密文”(即加密文本)的示例。对于一些较旧的算法,可以根据这些已知明文中的一个或几个来发现密钥。这使得解密和读取使用相同密钥加密的任何其他消息成为可能。

事实上,第二次世界大战中的Enigma machine 成为攻击的受害者,就像您所描述的那样 - 报告(例如天气报告)倾向于遵循固定的、可预测的结构,这使得盟军可以猜测他们的内容以获得已知的明文。然后,他们将使用已知的明文来恢复当天的密钥,并使用这些密钥来解密其他消息。非洲的一名官员在这方面特别有帮助,每天大约在同一时间回电“没有什么可报告的”。

顺便说一句,出于显而易见的原因,现代算法旨在防止已知明文攻击。您通常需要大量已知的明文才能成功恢复单个密钥。

话虽如此,现代算法不易受到这种攻击。即使攻击者确实发现了您的消息结构并能够“猜测”一些已知的明文,攻击者仍然需要大量的明文才能恢复密钥.

加密算法具有的另一个重要属性:明文中非常小的差异往往会在密文中产生很大的差异。例如,即使使用较旧的 DES 算法,更改单个位也可能导致密文中最多更改为 31 位。这就是所谓的avalanche effect。这将使获取有用信息变得更加困难,因为很难确定密文的哪一部分“对应”明文的哪一部分——弄清楚为什么一个特定的部分并非易事位被设置为没有访问原始密钥的方式。 无法保证密文的第 1 位被设置为由于明文第 1 位的值而被设置的方式,例如 - 就攻击者所知,它可能已被更改由于第 10 位的变化。

正如@ScottNewson 所述,this answer 也可能对您有所帮助。

考虑以下纯文本消息:

1001010100110101010101110

对应如下密文:

0110000111100110101000101

现在假设我将明文中的最后一位更改为 1:

1001010100110101010101111

这可能会改变密文中的多个位,这可能包括也可能不包括最后一位,因此没有理由相信它会导致类似

1001010100110101010101110

事实上,极不可能会导致这样的变化。

TL;DR 了解消息的结构对潜在的攻击者不会特别有帮助。

【讨论】:

  • 非常感谢。这正是我心中的问题。我将在这里放一个指向 Java 源代码的链接(它包括一个 GUI),但我认为最有用的是 API 方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-10
  • 2011-07-20
  • 2021-07-26
  • 2020-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多