Por que uma senha incorreta causa “O preenchimento é inválido e não pode ser removido”?
-
08-06-2019 - |
Pergunta
Eu precisava de uma criptografia de string simples, então escrevi o código a seguir (com muita "inspiração" de aqui):
// create and initialize a crypto algorithm
private static SymmetricAlgorithm getAlgorithm(string password) {
SymmetricAlgorithm algorithm = Rijndael.Create();
Rfc2898DeriveBytes rdb = new Rfc2898DeriveBytes(
password, new byte[] {
0x53,0x6f,0x64,0x69,0x75,0x6d,0x20, // salty goodness
0x43,0x68,0x6c,0x6f,0x72,0x69,0x64,0x65
}
);
algorithm.Padding = PaddingMode.ISO10126;
algorithm.Key = rdb.GetBytes(32);
algorithm.IV = rdb.GetBytes(16);
return algorithm;
}
/*
* encryptString
* provides simple encryption of a string, with a given password
*/
public static string encryptString(string clearText, string password) {
SymmetricAlgorithm algorithm = getAlgorithm(password);
byte[] clearBytes = System.Text.Encoding.Unicode.GetBytes(clearText);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, algorithm.CreateEncryptor(), CryptoStreamMode.Write);
cs.Write(clearBytes, 0, clearBytes.Length);
cs.Close();
return Convert.ToBase64String(ms.ToArray());
}
/*
* decryptString
* provides simple decryption of a string, with a given password
*/
public static string decryptString(string cipherText, string password) {
SymmetricAlgorithm algorithm = getAlgorithm(password);
byte[] cipherBytes = Convert.FromBase64String(cipherText);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, algorithm.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();
return System.Text.Encoding.Unicode.GetString(ms.ToArray());
}
O código parece funcionar bem, exceto que, ao descriptografar dados com uma chave incorreta, recebo uma CryptographicException - "O preenchimento é inválido e não pode ser removido" - na linha cs.Close() em decryptString.
código de exemplo:
string password1 = "password";
string password2 = "letmein";
string startClearText = "The quick brown fox jumps over the lazy dog";
string cipherText = encryptString(startClearText, password1);
string endClearText = decryptString(cipherText, password2); // exception thrown
Minha pergunta é: isso é esperado?Eu teria pensado que descriptografar com a senha errada resultaria apenas em uma saída sem sentido, em vez de uma exceção.
Solução
Embora este tenha sido respondidas Eu acho que seria uma boa idéia para explicar por é de se esperar.
Um esquema de preenchimento é normalmente aplicada porque a maioria dos filtros de criptografia não são semanticamente seguro e para evitar algumas formas de cryptoatacks. Por exemplo, geralmente em RSA a OAEP esquema de estofamento é utilizado o que impede que alguns tipos de ataques (tal como um ataque de texto plano escolhido ou cegueira ).
É criado um mecanismo de preenchimento acrescenta alguns (geralmente) de lixo aleatório para a mensagem m antes que a mensagem é enviada. No método OAEP, por exemplo, duas Oráculos são usadas (esta é uma explicação simplista):
- Dado o tamanho do módulo você PADD k1 pedaços com 0 e k0 bits com um número aleatório.
- Em seguida, aplicando alguma transformação para a mensagem que você obter a mensagem acolchoada wich é criptografado e enviado.
Isso fornece uma randomização para as mensagens e com uma maneira de testar se a mensagem é lixo ou não. Como o esquema de preenchimento é reversível, quando você descriptografar a mensagem enquanto você não pode dizer nada sobre a integridade da mensagem em si pode, de fato, fazer alguma afirmação sobre o preenchimento e, portanto, você pode saber se a mensagem foi correctamente descodificada ou você está fazendo algo errado (ou seja, alguém adulterou a mensagem ou você está usando a tecla errada)
Outras dicas
Eu experimentei um similar "Preenchimento é inválido e não pode ser removido." exceção, mas no meu caso o IV chave e estofamento estavam corretas.
Acontece que liberar o fluxo de criptografia é tudo o que estava faltando.
Como esta:
MemoryStream msr3 = new MemoryStream();
CryptoStream encStream = new CryptoStream(msr3, RijndaelAlg.CreateEncryptor(), CryptoStreamMode.Write);
encStream.Write(bar2, 0, bar2.Length);
// unless we flush the stream we would get "Padding is invalid and cannot be removed." exception when decoding
encStream.FlushFinalBlock();
byte[] bar3 = msr3.ToArray();
Se você quiser que o seu uso seja correta, você deve adicionar autenticação para seu texto cifrado assim que você pode verificar se ele é o pasword correta ou que o texto cifrado não foi modificado. O preenchimento você estiver usando ISO10126 só vai lançar uma exceção se a última byte não desencriptar como um dos 16 valores válidos para estofamento (0x01-0x10). Então você tem uma chance de ele não jogar a exceção com a senha errada, onde se você autenticá-lo você tem uma maneira determinista saber se o seu descriptografia é válido 1/16.
Usando API de criptografia é embora aparentemente fácil, na verdade é bastante é fácil cometer erros. Por exemplo, você usar um sal fixo para para você chave derivação e iv, isso significa que cada texto cifrado criptografado com a mesma senha irá reutilizá-lo do IV com essa chave, que quebra a segurança semântica com o modo CBC, os IV precisa ser imprevisível e única para uma determinada chave.
Por essa razão, de fácil cometer erros, eu tenho um trecho de código, que eu tento manter revisto e atualizado (comentários, questões bem-vindos):
exemplos modernos de Symmetric autenticados Encryption de uma string C #.
Se você usá-lo de AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password)
quando a senha errada é usado, null
é devolvido, se o texto cifrado ou iv foi modificado pós null
criptografia é retornado, você nunca vai voltar dados de lixo, ou uma exceção de preenchimento.
Se você já descartou key-incompatibilidade, então além FlushFinalBlock()
(ver resposta de Yaniv), chamando Close()
na CryptoStream
também será suficiente.
Se você está limpeza de recursos estritamente com blocos using
, certifique-se de ninho do bloco para o próprio CryptoStream
:
using (MemoryStream ms = new MemoryStream())
using (var enc = RijndaelAlg.CreateEncryptor())
{
using (CryptoStream encStream = new CryptoStream(ms, enc, CryptoStreamMode.Write))
{
encStream.Write(bar2, 0, bar2.Length);
} // implicit close
byte[] encArray = ms.ToArray();
}
Eu fui mordido por isso (ou similar):
using (MemoryStream ms = new MemoryStream())
using (var enc = RijndaelAlg.CreateEncryptor())
using (CryptoStream encStream = new CryptoStream(ms, enc, CryptoStreamMode.Write))
{
encStream.Write(bar2, 0, bar2.Length);
byte[] encArray = ms.ToArray();
} // implicit close -- too late!
Sim, isso é de se esperar, ou pelo menos, o seu exatamente o que acontece quando nossas rotinas criptográficas obter dados não-decryptable
Outra razão da exceção pode ser uma condição de corrida entre vários segmentos usando lógica de decodificação - implementações nativas de ICryptoTransform são não thread-safe (por exemplo SymmetricAlgorithm), por isso deve ser condenado à seção exclusiva, por exemplo usando Bloqueio . Por favor, consulte aqui para mais detalhes: http: / /www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/
Pode haver alguns bytes não lidas na CryptoStream. Fechando antes de ler o fluxo completamente estava causando o erro no meu programa.
Eu tive um problema semelhante, o problema no método de descriptografar foi inicializar um fluxo de memória vazio. quando ele trabalhou quando eu inicializado com a matriz de bytes texto cifrado assim:
MemoryStream ms = new MemoryStream(cipherText)
A resposta atualizada pelo usuário "atconway" funcionou para mim.
O problema não era com o preenchimento mas a chave que foi diferente durante a criptografia e descriptografia. A chave e iv deve ser o mesmo durante encypting e descriptografar o mesmo valor.