2015-01-23 15 views
7

Widzę niewielki procent użytkowników produkcyjnych losowo zgłasza ten wyjątek związany z szyfrowaniem/odszyfrowywanie ciągów za pomocą Xamarin.Android, ale niestety nie mogę go odtworzyć.CryptographicException: Bad PKCS7 padding

Co może spowodować to i/lub jak mogę odtworzyć wyjątek, aby można było ustalić poprawkę/obejść?

[CryptographicException: Bad PKCS7 padding. Invalid length 147.] 
    Mono.Security.Cryptography.SymmetricTransform.ThrowBadPaddingException(PaddingMode padding, Int32 length, Int32 position):0 
    Mono.Security.Cryptography.SymmetricTransform.FinalDecrypt(System.Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount):0 
    Mono.Security.Cryptography.SymmetricTransform.TransformFinalBlock(System.Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount):0 
    System.Security.Cryptography.CryptoStream.FlushFinalBlock():0 
    com.abc.mobile.shared.Security+PasswordEncoder.DecryptWithByteArray(System.String strText, System.String strEncrypt):0 

EDIT: Oto kod używam do szyfrowania/deszyfrowania

private string EncryptWithByteArray(string inPassword, string inByteArray) 
    { 

     byte[] tmpKey = new byte[20]; 
     tmpKey = System.Text.Encoding.UTF8.GetBytes(inByteArray.Substring(0, 8)); 
     DESCryptoServiceProvider des = new DESCryptoServiceProvider(); 
     byte[] inputArray = System.Text.Encoding.UTF8.GetBytes(inPassword); 
     MemoryStream ms = new MemoryStream(); 
     CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(tmpKey, mInitializationVector), CryptoStreamMode.Write); 
     cs.Write(inputArray, 0, inputArray.Length); 
     cs.FlushFinalBlock(); 
     return Convert.ToBase64String(ms.ToArray()); 

    } 

     private string DecryptWithByteArray (string strText, string strEncrypt) 
     { 

      try 
      { 
       byte[] tmpKey = new byte[20]; 
       tmpKey = System.Text.Encoding.UTF8.GetBytes (strEncrypt.Substring (0, 8)); 
       DESCryptoServiceProvider des = new DESCryptoServiceProvider(); 
       Byte[] inputByteArray = Convert.FromBase64String (strText); 
       MemoryStream ms = new MemoryStream(); 
       CryptoStream cs = new CryptoStream (ms, des.CreateDecryptor (tmpKey, mInitializationVector), CryptoStreamMode.Write); 
       cs.Write (inputByteArray, 0, inputByteArray.Length); 
      try { 
       cs.FlushFinalBlock(); 
      } catch (Exception ex) { 
       throw(ex); 
      } 
      System.Text.Encoding encoding = System.Text.Encoding.UTF8; 
      return encoding.GetString(ms.ToArray()); 
      } 
      catch (Exception ex) 
      { 
       throw ex; 
      } 
     } 

Edytowanie 2:

Klucz szyfrowania jest zawsze lokalna ID urządzenia. Oto jak ja dostaję to:

 TelephonyManager telephonyMgr = Application.Context.GetSystemService(Context.TelephonyService) as TelephonyManager; 
     string deviceId = telephonyMgr.DeviceId == null ? "UNAVAILABLE" : telephonyMgr.DeviceId; 

Oto przykład tego, jak to się nazywa:

string mByteArray = GetDeviceId(); 
string mEncryptedString = EncryptWithByteArray(stringToEncrypt, mByteArray); 
string mDecryptedString = DecryptWithByteArray(mEncryptedString, mByteArray); 
+0

Jaki szyfr blokowy i tryb działania? Czy możesz dostarczyć kopię wiadomości, której nie udało się odszyfrować? 147 wydaje się dziwną długością. To * zwykle * powinno być wielokrotnością długości bloku szyfru blokowego (często 16). Jednak tryby takie jak CTS i CTR nie mają tego wymogu. Zakładając tryb taki jak CBC, wygląda na to, że przetwarzana jest niekompletna wiadomość. – jww

+0

Dodałem kod do mojego originalquestion =) –

+0

na podstawie Twojej edycji i kodu przy użyciu DES, 147 *** jest źle. Rozmiar bloku DES wynosi 8 bajtów, więc wiadomość musi być wielokrotnością 8 bajtów. Znajdź brakujące 3 bajty (powinno to być 152 bajty), a twój problem zostanie rozwiązany. Powinieneś także używać 'byte []', a nie 'String', aby zaszyfrować dane. Zaszyfrowane dane mogą zawierać bajt 0x00, co może powodować problemy. Lub Base64 danych, aby obsłużyć bajty NULL. – jww

Odpowiedz

3

nie dostarczyły wiele szczegółów o swoim przypadku użycia, ale chciałbym powiedzieć, że to się dzieje, ponieważ nie są przy użyciu tych samych ustawień szyfrowania podczas operacji szyfrowania i deszyfrowania. Szyfrowanie symetryczne wymaga użycia dokładnie takich samych ustawień/parametrów podczas szyfrowania danych, a także odszyfrowywania. Na przykład dla AES CBC trzeba użyć dokładnie tego samego klucza, IV, trybu szyfrowania i dopełnienia na obu urządzeniach. Najlepiej jest, aby ustawić te ustawienia bezpośrednio w kodzie:

System.Security.Cryptography.RijndaelManaged aes = new System.Security.Cryptography.RijndaelManaged(); 
aes.Key = new byte[] { ... }; 
aes.IV = new byte[] { ... }; 
aes.Mode = CipherMode.CBC; 
aes.Padding = PaddingMode.PKCS7; 

Jeżeli jesteś pewien, że są przy użyciu tych samych ustawień następnie należy również rozważyć scenariusz, że niektóre dane zostaną uszkodzone lub zmienione podczas transferu sieciowego.

Edit po niektóre fragmenty kodu zostały dostarczone:

metody deszyfrowania podałeś nie działa dla mnie w ogóle więc mam połączyć wszystkie próbki i zwrócił je do kodu, który robi to samo jako twój, ale używa IMO nieco czystszego podejścia. Na przykład ten kod używa bardziej niezawodnego "wyprowadzania klucza" (proszę mi wybaczyć cryptoguys), a także przeszedł podstawową analizę kodu.

Powinieneś być w stanie łatwo użyć metody publiczne zrobić to, czego potrzebujesz:

string plainData = "This information should be encrypted"; 
string encryptedData = EncryptStringified(plainData); 
string decryptedData = DecryptStringified(encryptedData); 
if (plainData != decryptedData) 
    throw new Exception("Decryption failed"); 

Wdrożenie i metody prywatne następująco:

/// <summary> 
/// Encrypts string with the key derived from device ID 
/// </summary> 
/// <returns>Base64 encoded encrypted data</returns> 
/// <param name="stringToEncrypt">String to encrypt</param> 
public string EncryptStringified(string stringToEncrypt) 
{ 
    if (stringToEncrypt == null) 
     throw new ArgumentNullException("stringToEncrypt"); 

    byte[] key = DeviceIdToDesKey(); 
    byte[] plainData = Encoding.UTF8.GetBytes(stringToEncrypt); 
    byte[] encryptedData = Encrypt(key, plainData); 
    return Convert.ToBase64String(encryptedData); 
} 

/// <summary> 
/// Decrypts Base64 encoded data with the key derived from device ID 
/// </summary> 
/// <returns>Decrypted string</returns> 
/// <param name="b64DataToDecrypt">Base64 encoded data to decrypt</param> 
public string DecryptStringified(string b64DataToDecrypt) 
{ 
    if (b64DataToDecrypt == null) 
     throw new ArgumentNullException("b64DataToDecrypt"); 

    byte[] key = DeviceIdToDesKey(); 
    byte[] encryptedData = Convert.FromBase64String(b64DataToDecrypt); 
    byte[] decryptedData = Decrypt(key, encryptedData); 
    return Encoding.UTF8.GetString(decryptedData); 
} 

private byte[] DeviceIdToDesKey() 
{ 
    TelephonyManager telephonyMgr = Application.Context.GetSystemService(Context.TelephonyService) as TelephonyManager; 
    string deviceId = telephonyMgr.DeviceId ?? "UNAVAILABLE"; 

    // Compute hash of device ID so we are sure enough bytes have been gathered for the key 
    byte[] bytes = null; 
    using (SHA1 sha1 = SHA1.Create()) 
     bytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(deviceId)); 

    // Get last 8 bytes from device ID hash as a key 
    byte[] desKey = new byte[8]; 
    Array.Copy(bytes, bytes.Length - desKey.Length, desKey, 0, desKey.Length); 
    return desKey; 
} 

private byte[] Encrypt(byte[] key, byte[] plainData) 
{ 
    if (key == null) 
     throw new ArgumentNullException("key"); 

    if (plainData == null) 
     throw new ArgumentNullException("plainData"); 

    using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider()) 
    { 
     if (!desProvider.ValidKeySize(key.Length * 8)) 
      throw new CryptographicException("Key with invalid size has been specified"); 
     desProvider.Key = key; 
     // desProvider.IV should be automatically filled with random bytes when DESCryptoServiceProvider instance is created 
     desProvider.Mode = CipherMode.CBC; 
     desProvider.Padding = PaddingMode.PKCS7; 

     using (MemoryStream encryptedStream = new MemoryStream()) 
     { 
      // Write IV at the beginning of memory stream 
      encryptedStream.Write(desProvider.IV, 0, desProvider.IV.Length); 

      // Perform encryption and append encrypted data to the memory stream 
      using (ICryptoTransform encryptor = desProvider.CreateEncryptor()) 
      { 
       byte[] encryptedData = encryptor.TransformFinalBlock(plainData, 0, plainData.Length); 
       encryptedStream.Write(encryptedData, 0, encryptedData.Length); 
      } 

      return encryptedStream.ToArray(); 
     } 
    } 
} 

private byte[] Decrypt(byte[] key, byte[] encryptedData) 
{ 
    if (key == null) 
     throw new ArgumentNullException("key"); 

    if (encryptedData == null) 
     throw new ArgumentNullException("encryptedData"); 

    using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider()) 
    { 
     if (!desProvider.ValidKeySize(key.Length * 8)) 
      throw new CryptographicException("Key with invalid size has been specified"); 
     desProvider.Key = key; 
     if (encryptedData.Length <= desProvider.IV.Length) 
      throw new CryptographicException("Too short encrypted data has been specified"); 
     // Read IV from the beginning of encrypted data 
     // Note: New byte array needs to be created because data written to desprovider.IV are ignored 
     byte[] iv = new byte[desProvider.IV.Length]; 
     Array.Copy(encryptedData, 0, iv, 0, iv.Length); 
     desProvider.IV = iv; 
     desProvider.Mode = CipherMode.CBC; 
     desProvider.Padding = PaddingMode.PKCS7; 

     // Remove IV from the beginning of encrypted data and perform decryption 
     using (ICryptoTransform decryptor = desProvider.CreateDecryptor()) 
      return decryptor.TransformFinalBlock(encryptedData, desProvider.IV.Length, encryptedData.Length - desProvider.IV.Length); 
    } 
} 

Jest naprawdę trudno powiedzieć, co dokładnie było problemu z Twój kod, ponieważ twoja metoda odszyfrowywania nie działa dla mnie w ogóle - najprawdopodobniej dlatego, że używa CryptoStream w trybie zapisu do odszyfrowania, co wydaje mi się trochę dziwne.

Tyle za kod. Teraz przejdźmy do szyfrowania, które jest naprawdę bardzo słabe. Jest to po prostu zaciemnienie, które powinno chronić dane przed przypadkowym wyświetlaniem w formie zwykłego tekstu (niektórzy używają kodowania BASE64 dla tej samej rzeczy). Główną przyczyną tego jest stosunkowo stary algorytm szyfrowania i łatwy do przewidzenia klucz szyfrowania. AFAIK każda aplikacja działająca na tym samym urządzeniu może odczytywać identyfikator urządzenia bez żadnych uprawnień. Oznacza to, że każda aplikacja może odszyfrować dane. Oczywiście twoja baza danych SQLite jest prawdopodobnie dostępna tylko dla twojej aplikacji, ale to nie może być już prawdą, jeśli usuniesz kartę SD lub rootujesz telefon.Aby uczynić to nieco lepszym, można na przykład poprosić użytkownika o podanie hasła, a następnie użyć go do uzyskania unikalnego klucza szyfrowania, ale jest to zupełnie inny problem. W każdym razie nie jestem pewien, co próbujesz osiągnąć za pomocą tego szyfrowania - może być w pełni wystarczający dla twoich potrzeb, nawet jeśli można go uznać za słaby.

Mam nadzieję, że to pomoże.

+0

Proszę zobaczyć moje edycje powyżej RE metod, których używam do szyfrowania/odszyfrowywania. Niestety nie znalazłem sposobu na powielenie błędu - jest to zgłaszane przez naszą implementację raportowania błędów. Próbowałem rozmyślnie bawić się z wyściółką i ciągami szyfrowania, ale spowodowało to inny błąd - chociaż mój brak wiedzy z szyfrowaniem również mógł spowodować straty. –

+0

@ Le-roy implementacja tych metod wydaje mi się trochę dziwna, ale może to być spowodowane brakującym kontekstem. Czy mógłbyś opisać parametry wejściowe, ponieważ nie jestem pewien, czy rozumiem, co próbujesz zaszyfrować i skąd pochodzi klucz szyfrowania i wektor inicjowania. Pomogłoby to również, gdybyś mógł rozszerzyć swój kod o część, która faktycznie wywołuje te dwie metody. – jariq

+0

Dzięki Jariq. Szyfruję klucz API, a następnie zapisuję go w bazie danych SQLite na urządzeniu. Klucz szyfrowania to identyfikator urządzenia i nie jestem pewien, co masz na myśli przez Initialization Vector! (oprócz szybkiego Google'a właśnie teraz). Początkowo dostosowałem ten kod z czegoś, co znalazłem gdzie indziej w sieci (wiem!). Będę edytować mój OP powyżej z dodatkowym kodem. –