Эта статья представляет собой подробную версию того, чем я поделился внутри компании. Как следует из названия, я буду использовать примеры текста и кода, чтобы помочь вам полностью понять, почему мы не рекомендуем вам использовать режим шифрования CBC и что произойдет, если вы это сделаете. Используйте его. Вопросы безопасности, даже если вам необходимо его использовать, вам нужно обратить внимание на то, на какие аспекты.
Примечание. В этой статье рассматриваются только вопросы безопасности и не рассматриваются такие факторы, как производительность и совместимость.
Рабочий режим блочного шифрования не имеет ничего общего с конкретным алгоритмом блочного шифрования, поэтому, пока используется режим cbc, проблемы будут возникать не только у AES, DES, 3DES и других алгоритмов.
кAES-128-CBC
Например,Может защитить внутреннюю реализацию алгоритма AES.,Относитесь к алгоритму AES как к черному ящику.,Введите открытый текст и ключ и верните зашифрованный текст.
Поскольку это алгоритм блочного шифрования, длинный открытый текст необходимо группировать в соответствии с размером блока, согласованным с алгоритмом. Каждая группа AES имеет размер 16 байт. Если разные группы используют один и тот же ключ для вычислений, возникнут некоторые проблемы безопасности, поэтому по порядку. Чтобы применить блочные шифры в различных практических приложениях, NIST определил несколько режимов работы. В разных режимах используется разная логика обработки шифрования. К общим режимам работы относятся:
модель | описывать |
---|---|
ЕЦБ (Электронная кодовая книга) | Тот же ключ используется для шифрования групп открытого текста. |
CBC (ссылка на группу) | Входными данными для алгоритма шифрования является XOR предыдущей группы зашифрованного текста и текущей группы открытого текста. |
CFB (криптографическая обратная связь) | При одновременной обработке нескольких битов предыдущий блок зашифрованного текста используется в качестве входных данных следующего блока алгоритма шифрования, а псевдослучайное число подвергается операции XOR с открытым текстом или используется в качестве зашифрованного текста следующего блока. |
OFB (выходная обратная связь) | Подобно CFB, только вход алгоритма шифрования является выходом последнего шифрования, и используется вся группа. |
CTR (технический счетчик) | Каждый пакет открытого текста подвергается операции XOR с зашифрованным счетчиком. Увеличивать счетчик для каждого последующего пакета |
Режим ECB является самым простым. Предположим, что существуют группы открытого текста a, b, c, d. Каждая группа зашифрована с помощью одного и того же ключа k. Зашифрованный текст — A, B, C, D. Окончательный зашифрованный текст соответствует. открытый текст abcd — это ABCD, как показано на рисунке:
Режим ECB очень прост и может быть очень выгоден с точки зрения производительности, поскольку между группами нет корреляции и его можно рассчитывать независимо и параллельно. Однако с точки зрения безопасности этот метод непосредственной группировки зашифрованного текста для сращивания, вероятно, позволит злоумышленнику угадать характеристики открытого текста или заменить и отбросить некоторые блоки зашифрованного текста, чтобы добиться эффекта замены и перехвата открытого текста. рисунок очень понятен:
<img src="https://9eek-1251521991.cos.ap-chengdu.myqcloud.com/article/img/20230302102403.png" alt="image-20230302102403380" style="zoom:67%;" />
Поэтому легко понять, что ЕЦБ не является рекомендуемым режимом работы.
Используя уроки, извлеченные из ECB, модель CBC (Cipher Block Chaining) предлагает метод XOR группы открытого текста перед группой случайных значений IV и XOR зашифрованного текста этой группы с открытым текстом следующей группы. Этот метод Случайность. зашифрованный текст увеличивается, и проблема ЕЦБ устраняется. Подробный процесс показан на рисунке:
Поясните этот рисунок. Существуют группы открытого текста a, b, c, d. Режим работы cbc имеет порядок выполнения, то есть вторая группа может быть вычислена только после того, как будет вычислена первая группа открытого текста. шифрование a должно быть подвергнуто операции XOR с начальной группой IV. Прямо сейчас a^IV
,Затем используйте ключ K для выполнения стандартного шифрования AES.,E(a^IV,K)
Получите группу зашифрованного текста A первой группы. Группа зашифрованного текста A будет участвовать в вычислении второй группы зашифрованного текста. Процесс расчета аналогичен, за исключением того, что IV необходимо заменить на A во второй раз. Цикл продолжается. до тех пор, пока не будет получен окончательный зашифрованный текст ABCDПрямо. сейчасдляCBCмодель。
Внимательно наблюдайте за процессом шифрования CBC. Вам необходимо использовать случайную группу IV. В стандартном процессе шифрования IV будет объединен в группу зашифрованного текста. Предположим, что есть два человека, A и B. Сторона A дает. фактический зашифрованный текст для стороны B. Это (IV)ABCD. B извлекает IV после получения зашифрованного текста, а затем расшифровывает следующую цифру:
Процесс расшифровки заключается в том, что процесс шифрования меняет направление,Обратите внимание на направление стрелок от abcd до ABCD на двух графиках. Первая группа зашифрованного текста сначала расшифровывается с помощью AES.,Полученное промежуточное значение считаем как M_A,Затем M_A выполняет XOR исходного вектора IV, чтобы получить,Повторите то же действие для второй группы.,Или замените IV на группу зашифрованного текста A.,Наконец, можно получить группу открытого текста abcd.
CBC добавляет случайную переменную IV, чтобы добавить случайности к зашифрованному тексту и повысить сложность анализа зашифрованного текста. Безопасно ли это? Ответ, конечно, нет. CBC создала новую проблему: открытый текст можно изменить, изменив зашифрованный текст.
Атака с переворотом байта Принцип CBC очень прост, как показано на рисунке:
Атаки часто происходят в процессе расшифровки,Хакеры могут изменять открытый текст, контролируя группировку IV и зашифрованного текста.,На рисунке хакер может изменить исходный открытый текст d, заменив группу D зашифрованного текста на группу E (что может включать проверку заполнения).,Не беспокойтесь об этом здесь),Или, по тому же принципу, хакер может изменить группу открытого текста a, управляя IV.
Далее на практическом примере демонстрируется его принцип и вред.
Для обеспечения удобства Объяснение принципа,IV и ключ будут жестко записаны во время шифрования.,Избегайте разных результатов при каждом запуске.
Фальшивый Предположим, существуетwebСервисное приложение,Передняя и задняя части используют файлы cookie для проверки разрешений.,cookie的内容для明文admin:0
руководитьAES-128-CBC加密后的密文руководитьbase64кодирование,Число 0 означает, что права пользователя в данный момент не являются правами администратора.,Когда число после admin равно 1,Серверная часть будет думать, что это пользователь-администратор.
Cookie内容для:AAAAAAAAAAAAAAAAAAAAAJyycJTyrCtpsXM3jT1uVKU=
в это время Хакеры могут использовать Атаку, если знают принцип проверки. с переворотом байта начала атаку на этот сервис,Изменить открытый текст cookie, не зная ключа наadmin:1
,Конкретный процесс:
AES использует 16B в качестве блока. sizeруководить分块,admin:0
существоватьasciiкодирование下对应的二进制仅для7B,Следовательно, во время шифрования исходный открытый текст будет шифроваться до тех пор, пока его значение не станет в точности целым числом, кратным 16B.,Значит нам тоже понадобится наполнение 9B (подробности о наполнении будут рассмотрены ниже),Потому что у CBC также будет IV,Таким образом, окончательный зашифрованный текст — IV+Cipher.,IV16B,cipher16B,Всего 32Б,Здесь потому, что существует только один блок зашифрованного текста.,所к改变IVпринадлежащий7байты, соответствующие простому текстуadmin:0
позиция номера,Или 7-й байт зашифрованного текста Прямо Сейчас можно изменять поля цифровой части открытого текста. Путем непрерывных попыток мы сгруппировали исходный зашифрованный текст IV. 00
Изменить на01
,Прямо сейчас можно успешно перевернуть открытый текст на 1, Прямо сейчасcookie обычный текст становитсяadmin:1
, тем самым достигая цели повышения привилегий.
Полный код:
package com.example.springshiroproject;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Key;
import java.util.Arrays;
public class MyTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
AesCipherService aesCipherService = new AesCipherService();
// Жестко запрограммированный ключ
byte[] key = new byte[128/8];
Arrays.fill(key,(byte) '\0'); // Жестко запрограммированные ключи, неизвестные клиентам и хакерам.
String plainText = "admin:0"; // Простое текстовое содержимое файлов cookie
byte[] plainTextBytes = plainText.getBytes();
// Жестко закодированный IV
byte[] iv_bytes = new byte[128/8];
Arrays.fill(iv_bytes, (byte) '\0');
//
// // Метод шифрования AES-128-cbc для IV можно настроить с помощью вызова отражения (исходный метод является частным).
Method encryptWithIV = aesCipherService.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("encrypt",new Class[]{byte[].class, byte[].class,byte[].class,boolean.class});
encryptWithIV.setAccessible(true);
ByteSource cipherWithIV = (ByteSource) encryptWithIV.invoke(aesCipherService,new Object[]{plainTextBytes, key,iv_bytes,true});
System.out.println("Обычный текст:" + ByteSource.Util.bytes(plainTextBytes).toHex());
// Нормальная логическая расшифровка
byte[] cipher = cipherWithIV.getBytes();
System.out.println("Исходный зашифрованный текст: " + cipherWithIV.toHex());
System.out.println("Содержимое файла cookie: " + cipherWithIV.toBase64());
ByteSource decPlain = aesCipherService.decrypt(cipher, key);
System.out.println("Исходный расшифрованный текст:" + new String(decPlain.getBytes()));
// Атака с переворотом байта
cipher[6] = (byte)0x01;
System.out.println("Перевернуть зашифрованный текст: " + ByteSource.Util.bytes(cipher).toHex());
System.out.println("Перевернуть cookie:"+ ByteSource.Util.bytes(cipher).toBase64());
decPlain = aesCipherService.decrypt(cipher, key);
System.out.println("Перевернуть расшифрованный текст:" + new String(decPlain.getBytes()));
}
}
В этом примере речь идет только о ситуации с одним блоком. В реальных сценариях могут быть задействованы несколько блоков, и попытка изменить одну группу зашифрованного текста с помощью нескольких блоков фактически повлияет на две группы открытого текста, требуя постоянного перенаправления в одной и той же позиции. -потребление для выполнения угадывания преобразования предыдущей группировки зашифрованного текста.
Поэтому, чтобы сделать его более удобным для использования, злоумышленник обнаружил, что терминал расшифровки проверит правила заполнения. Если проверка не удалась, будет выдано исключение. Подобно слепому внедрению SQL, оно предоставляет злоумышленнику дополнительную информацию. облегчает эксплуатацию уязвимости.
Поскольку оно предполагает использование правил наполнения, необходимо ввести основной тип заливки:
Тип заливки | описывать |
---|---|
NoPadding | без дополнения |
PKCS#5 | Фиксированный размер блока — 8B. |
PKCS#7 | Размер блока может быть от 1 до 255. |
ISO 10126 | Последний байт дополняется необходимой длиной, а остальные дополняются случайным образом. |
ANSI X9.23 | Последний байт заполняется необходимой длиной, а оставшийся байт заполняется нулями. |
ZerosPadding | наполнение |
Давайте сосредоточимся здесьPKCS#5
иPKCS#7
, Я обнаружил, что многие статьи, написанные сотрудниками службы безопасности, проблематичны в отношении этих двух типов заполнения модели описывания, например:
На самом деле все равноpkcs#5
все ещеpkcs#7
наполнение Все содержимое обязательнонаполнение Количество байтов в этом двоичном числе,pkcs#5
соответствует8Bдля标准分块руководитьнаполнение,pkcs#7
是可к不固定1~255Еду в Токио,Только в соответствии с RFC-соглашением AES.,размер блока фиксирован на 16B,所ксуществоватьAESпозвони внутрьpkcs#5
иpkcs#7
Нет никакой разницы。
Приведите пример,Фальшивыйнравиться存существовать明文helloworld
,Обычный текст на английском языке,Согласно ascii, каждый символ занимает 1B.,明文长度для10B
,Все еще нужнонаполнение6B
,наполнение内容для\x06
,最终分块内容для:helloworld\x06\x06\x06\x06\x06\x06
.
При расшифровке сервер проверит содержимое следующим образом:
Атака оракула заполнения использует метод атаки, заключающийся в подделке последнего байта заполнения группы зашифрованного текста, чтобы заставить сервер сообщить об ошибке, а затем предсказать открытый текст или сгенерировать новый зашифрованный текст.,Итак, оракул здесь означает предсказание.,Нам знакома не Oracle, материнская компания Java.
Предположим, мы получили строку зашифрованного текста, зашифрованного с помощью AES-128-CBC. Содержимое зашифрованного текста:
000000000000000000000000000000009cb27094f2ac2b69b173378d3d6e54a5
Первые 16B из всех нулей — это жестко закодированный IV, а остальная часть — настоящий зашифрованный текст. Обзор процесса расшифровки
Содержимое таблицы, отмеченное желтым цветом, контролируется злоумышленником. Если вы переворачиваете только байты, вы можете изменить только содержимое открытого текста, но мы не можем точно знать конкретное содержимое открытого текста, поэтому необходимо заполнить его. oracle Только что появился,Обычная бизнес-логика будет оценивать содержимое простого текста при расшифровке.,Если расшифрованный контент верен, 200 может быть возвращено.,Расшифровать Ошибка обычного текст возвращает 403,Однако если программе зашифрованного текста не удается проверить пароль, это может вызвать ошибку программы и выдать ошибку 500.
Злоумышленник будет использовать ошибку 500 для прохода и определения правильности промежуточного значения предположения.
Угадав промежуточное значение и выполнив XOR с известным IV, вы можете получить открытый текст.
Давайте воспользуемся приведенным сейчас примером для тестирования.,Пытаемся угадать последнее среднее значение,Выполнять перебор проверки на IV с 00-ff до тех пор, пока программа не сообщит об ошибке,получатьiv[15]
для0x08
Время не сообщалосьнаполнениеошибка,证明这个час候篡改后的明文最后一位应该для0x01
,XOR исходного текста и IV,可得中间值для0x08^0x01 = 0x09
, красная часть в таблице:
Перейти ко второму шагу,Угадай предпоследнюю цифру,Угадай предпоследнюю цифру需要满足篡改后的明文后两位都для0x02
,Поскольку последняя цифра является средним значением, она была рассчитана как 0x09, поэтому,последнийivдля:0x09^0x02 = 0x0B
,циклivпредпоследний от00~ff.получатьIV值для0x0B
час,Программа не сообщает об ошибке,所к中间值для0x02^0x0B=0x09
Повторяйте этот процесс до тех пор, пока не будут угаданы все промежуточные значения.
в это время,Мы можем сделать это, не зная ключа,根据中间值иIVвывести простой текстM^IV=P
(Mдля中间值,IV — исходный вектор, P — открытый текст).
Поскольку мы жестко запрограммировали iv на 00, открытый текст представляет собой значение ASCII, соответствующее M, то есть:
admin:0\09\09\09\09\09\09\09\09\09
09 за наполнение контента,字节去掉получать最终明文:admin:0
Соответствующий код (Java):
package com.example.springshiroproject;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.crypto.CryptoException;
import org.apache.shiro.util.ByteSource;
import javax.crypto.BadPaddingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Key;
import java.util.Arrays;
public class MyTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
int blockSize = 16;
AesCipherService aesCipherService = new AesCipherService();
// Жестко запрограммированный ключ
byte[] key = new byte[128/8];
Arrays.fill(key,(byte) '\0'); // Жестко запрограммированные ключи, неизвестные клиентам и хакерам.
String plainText = "admin:0"; // Простое текстовое содержимое файлов cookie
byte[] plainTextBytes = plainText.getBytes();
byte[] iv_bytes = new byte[128/8];
Arrays.fill(iv_bytes, (byte) '\0');
//
// // Метод шифрования AES-128-cbc, который позволяет настроить IV посредством вызова отражения.
Method encryptWithIV = aesCipherService.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("encrypt",new Class[]{byte[].class, byte[].class,byte[].class,boolean.class});
encryptWithIV.setAccessible(true);
ByteSource cipherWithIV = (ByteSource) encryptWithIV.invoke(aesCipherService,new Object[]{plainTextBytes, key,iv_bytes,true});
System.out.println("Обычный текст:" + ByteSource.Util.bytes(plainTextBytes).toHex());
byte[] cipher = cipherWithIV.getBytes();
// System.out.println(cipher.length);
System.arraycopy(cipher,0,iv_bytes,0,blockSize-1);
System.out.println("Исходный зашифрованный текст: " + cipherWithIV.toHex());
System.out.println("Содержимое файла cookie: " + cipherWithIV.toBase64());
ByteSource decPlain = aesCipherService.decrypt(cipher, key);
System.out.println("Исходный расшифрованный текст:" + new String(decPlain.getBytes()));
System.out.println("Начните пробовать");
decPlain = null;
byte[] middleValue = new byte[blockSize];
Arrays.fill(middleValue,(byte) 0x00);
boolean flipFlag = false;
for (int j=0; j<blockSize; j++){
byte tmp;
System.out.println("start "+ (j+1));
if (j >0){
for (int p=middleValue.length-1;p>middleValue.length-1-j;p--){
tmp = (byte) (middleValue[p]^(j+1));
cipher[p] = tmp;
// System.out.println("tmp в это время: " + tmp);
}
System.out.println("шифрование на основе известного промежуточного значения заполненияiv: " + ByteSource.Util.bytes(cipher).toHex());
}else {
System.out.println("исходныйнаполнение"); }
tmp = cipher[blockSize-j-1];
for (int i=0x00; i<=0xff; i++){
if (tmp == i){
// continue;
System.out.println("Пропустить то же, что и исходное значение");
if (!flipFlag){
flipFlag = true;
continue;
}
}
cipher[blockSize-j-1] = (byte) i;
try{
decPlain = aesCipherService.decrypt(cipher, key);
tmp = (byte) (i ^ (j+1));
middleValue[blockSize-j-1] =tmp; //Сохраняем промежуточное значение M = IV ^ I
System.out.println("Угадал! Последний" +(j+1) +"IV:" + i);
System.out.println("Предпоследний" +(j+1) +"М:" + tmp);
break;
}catch (CryptoException e){
if (i==0xff){
System.out.print("Не закончилось");
System.exit(0);
}
}
}
}
System.out.println("Промежуточное значение предположения:" + ByteSource.Util.bytes(middleValue).toHex());
byte[] attackPlain = new byte[blockSize];
for (int i=0;i<attackPlain.length;i++){
attackPlain[i] =(byte)( iv_bytes[i] ^middleValue[i]);
}
System.out.println("Окончательный зашифрованный текст:" + ByteSource.Util.bytes(cipher).toHex());
System.out.println("Окончательный простой текст:" + ByteSource.Util.bytes(attackPlain).toHex());
System.out.println("Попытка завершена");
System.out.println("Перевернуть расшифрованный текст:" + new String(attackPlain));
}
}
Результаты запуска:
Кроме того, я также написал соответствующую версию Python. Если вы создадите свое колесо и обнаружите ошибки, вы можете обратиться к моему коду:
Среда моделирования уязвимостей:
from aes_manual import aes_manual
class PaddingOracleEnv:
def __init__(self):
self.key = aes_manual.get_key(16)
def run(self):
cipher = aes_manual.encrypt(self.key, "hello".encode())
def login(self,cookie):
try:
text = aes_manual.decrypt(self.key, cookie)
if text == b'hello':
return 200 # абсолютно правильно
else:
return 403 # Ошибка обычного текста
except RuntimeError as e:
return 500 # наполнение Проверка не удалась
padding_oracle_env = PaddingOracleEnv()
if __name__ == '__main__':
res = padding_oracle_env.login(b"1111111111111111R\xbb\x16^\xaf\xa8\x18Me.U\xaf\xfe\xb6\x99\xec")
print(res)
Скрипт атаки:
import sys
from aes_manual import aes_manual
from padding_oracle_env import padding_oracle_env
from loguru import logger
class PaddingOracleAttack:
def __init__(self):
logger.remove()
logger.add(sys.stderr,level="DEBUG")
self.cipher_text_raw = b"1111111111111111R\xbb\x16^\xaf\xa8\x18Me.U\xaf\xfe\xb6\x99\xec"
self.iv = aes_manual.get_iv(self.cipher_text_raw)
self.cipher_content = aes_manual.get_cipher_content(self.cipher_text_raw)
def single_byte_xor(self, A: bytes, B: bytes):
"""Однобайтовая операция XOR"""
assert len(A) == len(B) == 1
return ord(A) ^ ord(B)
def guess_last(self):
"""
padding oracle
:return:
"""
c_l = len(self.cipher_content)
M = bytearray()
for j in range(1, c_l+1): # Медианные цифры
for i in range(1, 256): # Фальшивый iv взрывные работы
f_iv = b'\x00' * (c_l-j) + bytes([i])
for m in M[::-1]:
f_iv += bytes([m ^ j]) # Используйте m, известное на предыдущем шаге, чтобы позже вычислить iv неизвестного положения.
res = padding_oracle_env.login(f_iv + self.cipher_content)
if res == 403: # наполнениеправильная ситуация
M.append(i ^ j)
logger.info(f"{j} - {bytes([i])} - {i}")
break
# logger.info(M)
M = M[::-1] # reverse
logger.info(f"M({len(M)}):{M}")
p = bytearray()
for m_i, m in enumerate(M):
p.append(m ^ self.iv[m_i])
logger.info(f"Треснутый открытый текст ({len(p)}):{p}")
def run(self):
self.guess_last()
if __name__ == '__main__':
attack = PaddingOracleAttack()
attack.run()
На самом деле, нет необходимости изобретать велосипед.,Так же есть много готовых инструментов,нравиться:https://github.com/KishanBagaria/padding-oracle-attacker
Чтобы ответить на главный вопрос, именно из-за существования методов атаки, таких как переворот байтов CBC и атака оракула заполнения, рабочий режим CBC не рекомендуется в сценариях, требующих высокой конфиденциальности передачи.
А еще я есть в Google、поиск в байдушифрование AES CBC в Python
关键词час出现了很多误导性的文章:
И статья входит в тройку лучших,Пример кода внутри фактически использует ключ шифрования и дешифрования непосредственно в качестве IV.,Это сопряжено со следующими рисками:
Для обеспечения безопасности необходимо генерировать случайный и уникальный IV и хранить его вместе с зашифрованным текстом. Обычной практикой является создание нового IV для каждого шифрования и передача или сохранение его вместе как дополнительные данные зашифрованного текста, чтобы его можно было правильно использовать при расшифровке. Это позволяет избежать атак на предсказуемость и повышает безопасность режима AES CBC.
Более рекомендуется использовать GCM в качестве рабочего режима шифрования и дешифрования, потому что: