title: InvalidCipherTextException "Pad block corrupt" when decrypting string
body: <p>I am working on a mobile application using flutter/dart that will communicate with a Java RESTful service. I want to encrypt the user's password, even though we are on a private network, when they attempt to log in.</p>
  
  <p>On the flutter side, I am using the pointy castle dart package. On the Java side, the bouncy castle package. As the user goes to log in, I make a call to the web service to get a 32 randomly generated string and a 16 byte randomly generated string. These will be used as the encryption key and initialization vector respectively. The string are Base64 encoded before sending them back to the mobile application.</p>
  
  <p>When I go to decrypt the string in the RESTful service I receive the error</p>
  
  <p><code>javax.servlet.ServletException: org.glassfish.jersey.server.ContainerException: org.bouncycastle.crypto.InvalidCipherTextException: pad block corrupted</code></p>
  
  <p>For encryption and decryption on both sides I'm using AES/CBC/PKCS7 padding.</p>
  
  <pre><code>@GET
  @Path("key")
  public Response generateKey() throws Exception {
  
      String passphrase = RandomStringUtils.randomAlphanumeric(32);
      String encodedretVal = Base64.getEncoder().encodeToString(passphrase.getBytes("UTF-8"));
  
      System.out.println("Raw passphrase: " + passphrase);
      System.out.println("Encoded passphrase: " + encodedretVal);
  
      String iv = RandomStringUtils.randomAlphanumeric(16);
      String encodediv = Base64.getEncoder().encodeToString(iv.getBytes("UTF-8"));
  
      String response = new JSONObject()
              .put("passphrase", encodedretVal)
              .put("iv", encodediv).toString();
  
  
      return Response.ok(response, MediaType.APPLICATION_JSON).build();
  }
  </code></pre>
  
  <p>The values are decoded and used to encrypt the password. I am using the following code from this <a href="https://gist.github.com/proteye/e54eef1713e1fe9123d1eb04c0a5cf9b" rel="nofollow noreferrer">gist</a></p>
  
  <pre><code>import 'dart:convert';
  import 'dart:typed_data';
  
  import 'package:encryption_test/utilities/helpers/gylogger.dart';
  import "package:pointycastle/export.dart";
  
  // AES key size
  const KEY_SIZE = 32; // 32 byte key for AES-256
  const ITERATION_COUNT = 1000;
  
  class AesHelper {
    static const CBC_MODE = 'CBC';
    static const CFB_MODE = 'CFB';
  
    GyLogger _logger = GyLogger();
  
    static Uint8List deriveKey(dynamic password, {String salt = '', int iterationCount = ITERATION_COUNT, int derivedKeyLength = KEY_SIZE}) {
      if (password == null || password.isEmpty) {
        throw new ArgumentError('password must not be empty');
      }
  
      if (password is String) {
        password = createUint8ListFromString(password);
      }
  
      Uint8List saltBytes = createUint8ListFromString(salt);
      Pbkdf2Parameters params = new Pbkdf2Parameters(saltBytes, iterationCount, derivedKeyLength);
      KeyDerivator keyDerivator = new PBKDF2KeyDerivator(new HMac(new SHA256Digest(), 64));
      keyDerivator.init(params);
  
      return keyDerivator.process(password);
    }
  
    static Uint8List pad(Uint8List src, int blockSize) {
      var pad = new PKCS7Padding();
      pad.init(null);
  
      int padLength = blockSize - (src.length % blockSize);
      var out = new Uint8List(src.length + padLength)..setAll(0, src);
      pad.addPadding(out, src.length);
  
      return out;
    }
  
    static Uint8List unpad(Uint8List src) {
      var pad = new PKCS7Padding();
      pad.init(null);
  
      int padLength = pad.padCount(src);
      int len = src.length - padLength;
  
      return new Uint8List(len)..setRange(0, len, src);
    }
  
    static String encrypt(String password, String plaintext, String ivtext, {String mode = CBC_MODE}) {
      Uint8List derivedKey = deriveKey(password);
      KeyParameter keyParam = new KeyParameter(derivedKey);
      BlockCipher aes = new AESFastEngine();
  
      Uint8List iv = createUint8ListFromString(ivtext);
  
      BlockCipher cipher;
      ParametersWithIV params = new ParametersWithIV(keyParam, iv);
      switch (mode) {
        case CBC_MODE:
          cipher = new CBCBlockCipher(aes);
          break;
        case CFB_MODE:
          cipher = new CFBBlockCipher(aes, aes.blockSize);
          break;
        default:
          throw new ArgumentError('incorrect value of the "mode" parameter');
          break;
      }
      cipher.init(true, params);
  
      Uint8List textBytes = createUint8ListFromString(plaintext);
      Uint8List paddedText = pad(textBytes, aes.blockSize);
      Uint8List cipherBytes = _processBlocks(cipher, paddedText);
      Uint8List cipherIvBytes = new Uint8List(cipherBytes.length + iv.length)..setAll(0, iv)..setAll(iv.length, cipherBytes);
      Uint8List.view(cipherIvBytes.buffer);
  
      return base64.encode(cipherIvBytes);
    }
  
    static String decrypt(String password, String ciphertext, String ivtext, {String mode = CBC_MODE}) {
      Uint8List derivedKey = deriveKey(password);
      KeyParameter keyParam = new KeyParameter(derivedKey);
      BlockCipher aes = new AESFastEngine();
  
      Uint8List cipherIvBytes = base64.decode(ciphertext);
      Uint8List iv = new Uint8List(aes.blockSize)..setRange(0, aes.blockSize, cipherIvBytes);
  
      BlockCipher cipher;
      ParametersWithIV params = new ParametersWithIV(keyParam, iv);
      switch (mode) {
        case CBC_MODE:
          cipher = new CBCBlockCipher(aes);
          break;
        case CFB_MODE:
          cipher = new CFBBlockCipher(aes, aes.blockSize);
          break;
        default:
          throw new ArgumentError('incorrect value of the "mode" parameter');
          break;
      }
      cipher.init(false, params);
  
      int cipherLen = cipherIvBytes.length - aes.blockSize;
      Uint8List cipherBytes = new Uint8List(cipherLen)..setRange(0, cipherLen, cipherIvBytes, aes.blockSize);
      Uint8List paddedText = _processBlocks(cipher, cipherBytes);
      Uint8List textBytes = unpad(paddedText);
  
      return new String.fromCharCodes(textBytes);
    }
  
    static Uint8List _processBlocks(BlockCipher cipher, Uint8List inp) {
      var out = new Uint8List(inp.lengthInBytes);
  
      for (var offset = 0; offset &lt; inp.lengthInBytes;) {
        var len = cipher.processBlock(inp, offset, out, offset);
        offset += len;
      }
  
      return out;
    }
  
    static Uint8List createUint8ListFromString(String s) {
      var ret = new Uint8List(s.length);
      for (var i = 0; i &lt; s.length; i++) {
        ret[i] = s.codeUnitAt(i);
      }
      return ret;
    }
  }
  </code></pre>
  
  <p>The encrypted string, encoded key and encoded IV are sent back to the server where I attempt to decrypt the password. I got the following code from this Stack Exchange Code Review <a href="https://codereview.stackexchange.com/q/166997">request</a></p>
  
  <pre><code>@GET
  @Path("secret")
  public String decryptPassword(@QueryParam("phrase") String phrase, @QueryParam("key") String passphrase, @QueryParam("iv") String iv) throws Exception {
  
      passphrase = new String(decoder.decode(passphrase));
      iv = new String(decoder.decode(iv));
  
      AESEngine engine = new AESEngine();
      CBCBlockCipher blockCipher = new CBCBlockCipher(engine);
      BlockCipherPadding padding = new PKCS7Padding();
      PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(blockCipher, padding); 
      KeyParameter keyParam = new KeyParameter(passphrase.getBytes("UTF-8"));
      ParametersWithIV keyParamWithIV = new ParametersWithIV(keyParam, iv.getBytes("UTF-8"), 0, iv.getBytes("UTF-8").length);
  
      cipher.init(false, keyParamWithIV);
      byte[] out2 = decoder.decode(phrase);
  
      byte[] comparisonBytes = new byte[cipher.getOutputSize(out2.length)];
      int length = cipher.processBytes(out2, 0, out2.length, comparisonBytes, 0);
      cipher.doFinal(comparisonBytes, length); //Do the final block
      String decrypted = new String(comparisonBytes);
      System.out.println("Decrypted String:" + decrypted);
  
      return decrypted;
  }
  </code></pre>
  
  <p>Each time I try to decrypt the string I receive the following error</p>
  
  <pre><code>javax.servlet.ServletException: org.glassfish.jersey.server.ContainerException: org.bouncycastle.crypto.InvalidCipherTextException: pad block corrupted
      at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:408)
      at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:346)
      at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:365)
      at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:318)
      at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:205)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
      at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
      at com.gy.web.RequestLogger.doFilter(RequestLogger.java:64)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
      at com.gy.filters.AccessControlFilter.doFilter(AccessControlFilter.java:162)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
      at com.gy.filters.LocaleFilter.doFilter(LocaleFilter.java:54)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
      at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:186)
      at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
      at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
      at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
      at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
      at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:678)
      at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
      at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
      at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
      at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
      at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:836)
      at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1747)
      at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
      at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
      at java.lang.Thread.run(Thread.java:748)
  Caused by: org.glassfish.jersey.server.ContainerException: org.bouncycastle.crypto.InvalidCipherTextException: pad block corrupted
      at org.glassfish.jersey.servlet.internal.ResponseWriter.rethrow(ResponseWriter.java:254)
      at org.glassfish.jersey.servlet.internal.ResponseWriter.failure(ResponseWriter.java:236)
      at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:436)
      at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:261)
      at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
      at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
      at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
      at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
      at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
      at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265)
      at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:232)
      at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:680)
      at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:392)
      ... 35 more
  Caused by: org.bouncycastle.crypto.InvalidCipherTextException: pad block corrupted
      at org.bouncycastle.crypto.paddings.PKCS7Padding.padCount(Unknown Source)
      at org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher.doFinal(Unknown Source)
      at com.mfgweb.encryption.EncryptionController.decryptPassword(EncryptionController.java:121)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.lang.reflect.Method.invoke(Method.java:498)
      at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52)
      at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:124)
      at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:167)
      at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:219)
      at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:79)
      at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:469)
      at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:391)
      at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:80)
      at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:253)
      ... 44 more
  </code></pre>
  
  <p>In some additional research I found a suggestion to dump the byte array of the encrypted string from both sides, the mobile app and the service, and compare the two. Doing so revealed the following</p>
  
  <p>From Dart Encryption</p>
  
  <pre><code>[69,76,56,106,109,67,97,55,122,115,49,120,118,50,104,70,12,3,106,96,19,116,166,72,232,73,221,117,115,143,35,115]
  </code></pre>
  
  <p>From Java Decryption</p>
  
  <pre><code>[69,76,56,106,109,67,97,55,122,115,49,120,118,50,104,70,12,3,106,96,19,116,-90,72,-24,73,-35,117,115,-113,35,115]
  </code></pre>
  
  <p>Both are the same length but the Java byte array is showing negative numbers. I believe this is because, from what I understand, the Dart array is an array of unsigned integers where Java is signed. I don't know if this is the issue or not.</p>
  
  <p>I need some help trying to figure this out. What am I doing wrong?</p>