Android - QR码中AES加密字符串的解密问题

时间:2011-07-13 13:32:06

标签: android cryptography aes encryption

我已经建立了一个QR码生成器和一个二维码扫描器,用于在手机之间传递有关手机及其用户的数据(这些手机正在借出,因此将有一部带有扫描仪应用程序的主手机,其余部分带有生成器应用程序)。生成的QR代码是一个JSON格式字符串,其中包含其手机的人名/编号/ imei但为了安全起见,我尝试在编码到QR之前对字符串进行加密,但扫描的QR代码会引发“填充块损坏”错误。

JSON数据从QR精确编码为QR /解码为纯文本,我在编码到QR之前检查加密/解密,数据加密/解密很好,所以这与加密文本编码成时有关。 QR,但我不知道从哪里开始!

有谁知道我如何排序问题?或者,如果有任何QR友好的加密方法?!!

我直接从ZXings源获取QRCodeEncoder并将其放入我的活动中:

/**QR ENCODER CLASS****************************************************/

    public class QRCodeEncoder
    {
        private final String TAG = QRCodeEncoder.class.getSimpleName();

          private static final int WHITE = 0xFFFFFFFF;
          private static final int BLACK = 0xFF000000;

          private final Activity activity;
          private String contents;
          private String displayContents;
          private String title;
          private BarcodeFormat format;
          private final int dimension;

          QRCodeEncoder(Activity activity, Intent intent, int dimension) {
                this.activity = activity;
                if (intent == null) {
                  throw new IllegalArgumentException("No valid data to encode. intent is null");
                }

                String action = intent.getAction();
                if (action.equals(Intents.Encode.ACTION)) {
                  if (!encodeContentsFromZXingIntent(intent)) {
                    throw new IllegalArgumentException("No valid data to encode. Zxing intent returned false");
                  }
                } else if (action.equals(Intent.ACTION_SEND)) {
                  if (!encodeContentsFromShareIntent(intent)) {
                    throw new IllegalArgumentException("No valid data to encode. Share Intent returned false");
                  }
                }

                this.dimension = dimension;
              }

              public String getContents() {
                return contents;
              }

              public String getDisplayContents() {
                return displayContents;
              }

              public String getTitle() {
                return title;
              }

              // It would be nice if the string encoding lived in the core ZXing library,
              // but we use platform specific code like PhoneNumberUtils, so it can't.
              private boolean encodeContentsFromZXingIntent(Intent intent) {
                 // Default to QR_CODE if no format given.
                String formatString = intent.getStringExtra(Intents.Encode.FORMAT);
                try {
                  format = BarcodeFormat.valueOf(formatString);
                } catch (IllegalArgumentException iae) {
                  // Ignore it then
                  format = null;
                }
                if (format == null || BarcodeFormat.QR_CODE.equals(format)) {
                  String type = intent.getStringExtra(Intents.Encode.TYPE);
                  if (type == null || type.length() == 0) {
                    return false;
                  }
                  this.format = BarcodeFormat.QR_CODE;
                  encodeQRCodeContents(intent, type);
                } else {
                  String data = intent.getStringExtra(Intents.Encode.DATA);
                  if (data != null && data.length() > 0) {
                    contents = data;
                    displayContents = data;
                    title = "QR Encoder";
                  }
                }
                return contents != null && contents.length() > 0;
              }

              // Handles send intents from multitude of Android applications
              private boolean encodeContentsFromShareIntent(Intent intent) {
                // Check if this is a plain text encoding, or contact
                if (intent.hasExtra(Intent.EXTRA_TEXT)) {
                  return encodeContentsFromShareIntentPlainText(intent);
                }
                // Attempt default sharing.
                return encodeContentsFromShareIntentDefault(intent);
              }

              private boolean encodeContentsFromShareIntentPlainText(Intent intent) {
                // Notice: Google Maps shares both URL and details in one text, bummer!
                contents = intent.getStringExtra(Intent.EXTRA_TEXT);
                Toast.makeText(getApplicationContext(),"contents read = "+contents,Toast.LENGTH_SHORT).show();
                // We only support non-empty and non-blank texts.
                // Trim text to avoid URL breaking.
                if (contents == null) {
                  return false;
                }
                contents = contents.trim();
                if (contents.length() == 0) {
                  return false;
                }
                // We only do QR code.
                format = BarcodeFormat.QR_CODE;
                if (intent.hasExtra(Intent.EXTRA_SUBJECT)) {
                  displayContents = intent.getStringExtra(Intent.EXTRA_SUBJECT);
                } else if (intent.hasExtra(Intent.EXTRA_TITLE)) {
                  displayContents = intent.getStringExtra(Intent.EXTRA_TITLE);
                } else {
                  displayContents = contents;
                }
                title = "QR Encoder";
                return true;
              }

              // Handles send intents from the Contacts app, retrieving a contact as a VCARD.
              // Note: Does not work on HTC devices due to broken custom Contacts application.
              private boolean encodeContentsFromShareIntentDefault(Intent intent) {
                format = BarcodeFormat.QR_CODE;
                try {
                  Uri uri = (Uri)intent.getExtras().getParcelable(Intent.EXTRA_STREAM);
                  InputStream stream = activity.getContentResolver().openInputStream(uri);
                  int length = stream.available();
                  if (length <= 0) {
                    Log.w(TAG, "Content stream is empty");
                    return false;
                  }
                  byte[] vcard = new byte[length];
                  int bytesRead = stream.read(vcard, 0, length);
                  if (bytesRead < length) {
                    Log.w(TAG, "Unable to fully read available bytes from content stream");
                    return false;
                  }
                  String vcardString = new String(vcard, 0, bytesRead, "UTF-8");
                  Log.d(TAG, "Encoding share intent content:");
                  Log.d(TAG, vcardString);
                  Result result = new Result(vcardString, vcard, null, BarcodeFormat.QR_CODE);
                  ParsedResult parsedResult = ResultParser.parseResult(result);
                  if (!(parsedResult instanceof AddressBookParsedResult)) {
                    Log.d(TAG, "Result was not an address");
                    return false;
                  }
                  if (!encodeQRCodeContents((AddressBookParsedResult) parsedResult)) {
                    Log.d(TAG, "Unable to encode contents");
                    return false;
                  }
                } catch (IOException e) {
                  Log.w(TAG, e);
                  return false;
                } catch (NullPointerException e) {
                  Log.w(TAG, e);
                  // In case the uri was not found in the Intent.
                  return false;
                }
                return contents != null && contents.length() > 0;
              }

              private void encodeQRCodeContents(Intent intent, String type) {
                if (type.equals(Contents.Type.TEXT)) {
                  String data = intent.getStringExtra(Intents.Encode.DATA);
                  if (data != null && data.length() > 0) {
                    contents = data;
                    displayContents = data;
                    title = "QR Encoder";
                  }
                } else if (type.equals(Contents.Type.EMAIL)) {
                  String data = trim(intent.getStringExtra(Intents.Encode.DATA));
                  if (data != null) {
                    contents = "mailto:" + data;
                    displayContents = data;
                    title = "QR Encoder";
                  }
                } else if (type.equals(Contents.Type.PHONE)) {
                  String data = trim(intent.getStringExtra(Intents.Encode.DATA));
                  if (data != null) {
                    contents = "tel:" + data;
                    displayContents = PhoneNumberUtils.formatNumber(data);
                    title = "QR Encoder";
                  }
                } else if (type.equals(Contents.Type.SMS)) {
                  String data = trim(intent.getStringExtra(Intents.Encode.DATA));
                  if (data != null) {
                    contents = "sms:" + data;
                    displayContents = PhoneNumberUtils.formatNumber(data);
                    title = "QR Encoder";
                  }
                } else if (type.equals(Contents.Type.CONTACT)) {
                  Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
                  if (bundle != null) {
                    StringBuilder newContents = new StringBuilder(100);
                    StringBuilder newDisplayContents = new StringBuilder(100);
                    newContents.append("MECARD:");
                    String name = trim(bundle.getString(Contacts.Intents.Insert.NAME));
                    if (name != null) {
                      newContents.append("N:").append(escapeMECARD(name)).append(';');
                      newDisplayContents.append(name);
                    }
                    String address = trim(bundle.getString(Contacts.Intents.Insert.POSTAL));
                    if (address != null) {
                      newContents.append("ADR:").append(escapeMECARD(address)).append(';');
                      newDisplayContents.append('\n').append(address);
                    }
                    for (int x = 0; x < Contents.PHONE_KEYS.length; x++) {
                      String phone = trim(bundle.getString(Contents.PHONE_KEYS[x]));
                      if (phone != null) {
                        newContents.append("TEL:").append(escapeMECARD(phone)).append(';');
                        newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone));
                      }
                    }
                    for (int x = 0; x < Contents.EMAIL_KEYS.length; x++) {
                      String email = trim(bundle.getString(Contents.EMAIL_KEYS[x]));
                      if (email != null) {
                        newContents.append("EMAIL:").append(escapeMECARD(email)).append(';');
                        newDisplayContents.append('\n').append(email);
                      }
                    }
                    // Make sure we've encoded at least one field.
                    if (newDisplayContents.length() > 0) {
                      newContents.append(';');
                      contents = newContents.toString();
                      displayContents = newDisplayContents.toString();
                      title = "QR Encoder";
                    } else {
                      contents = null;
                      displayContents = null;
                    }
                  }
                } else if (type.equals(Contents.Type.LOCATION)) {
                  Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
                  if (bundle != null) {
                    // These must use Bundle.getFloat(), not getDouble(), it's part of the API.
                    float latitude = bundle.getFloat("LAT", Float.MAX_VALUE);
                    float longitude = bundle.getFloat("LONG", Float.MAX_VALUE);
                    if (latitude != Float.MAX_VALUE && longitude != Float.MAX_VALUE) {
                      contents = "geo:" + latitude + ',' + longitude;
                      displayContents = latitude + "," + longitude;
                      title = "QR Encoder";
                    }
                  }
                }
              }

              private boolean encodeQRCodeContents(AddressBookParsedResult contact) {
                StringBuilder newContents = new StringBuilder(100);
                StringBuilder newDisplayContents = new StringBuilder(100);
                newContents.append("MECARD:");
                String[] names = contact.getNames();
                if (names != null && names.length > 0) {
                  String name = trim(names[0]);
                  if (name != null) {
                    newContents.append("N:").append(escapeMECARD(name)).append(';');
                    newDisplayContents.append(name);
                  }
                }
                String[] addresses = contact.getAddresses();
                if (addresses != null) {
                  for (String address : addresses) {
                    address = trim(address);
                    if (address != null) {
                      newContents.append("ADR:").append(escapeMECARD(address)).append(';');
                      newDisplayContents.append('\n').append(address);
                    }
                  }
                }
                String[] phoneNumbers = contact.getPhoneNumbers();
                if (phoneNumbers != null) {
                  for (String phone : phoneNumbers) {
                    phone = trim(phone);
                    if (phone != null) {
                      newContents.append("TEL:").append(escapeMECARD(phone)).append(';');
                      newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone));
                    }
                  }
                }
                String[] emails = contact.getEmails();
                if (emails != null) {
                  for (String email : emails) {
                    email = trim(email);
                    if (email != null) {
                      newContents.append("EMAIL:").append(escapeMECARD(email)).append(';');
                      newDisplayContents.append('\n').append(email);
                    }
                  }
                }
                String url = trim(contact.getURL());
                if (url != null) {
                  newContents.append("URL:").append(escapeMECARD(url)).append(';');
                  newDisplayContents.append('\n').append(url);
                }
                // Make sure we've encoded at least one field.
                if (newDisplayContents.length() > 0) {
                  newContents.append(';');
                  contents = newContents.toString();
                  displayContents = newDisplayContents.toString();
                  title = "QR Encoder";
                  return true;
                } else {
                  contents = null;
                  displayContents = null;
                  return false;
                }
              }

              Bitmap encodeAsBitmap() throws WriterException {
                Hashtable<EncodeHintType,Object> hints = null;
                String encoding = guessAppropriateEncoding(contents);
                if (encoding != null) {
                  hints = new Hashtable<EncodeHintType,Object>(2);
                  hints.put(EncodeHintType.CHARACTER_SET, encoding);
                }
                MultiFormatWriter writer = new MultiFormatWriter();
                BitMatrix result = writer.encode(contents, format, dimension, dimension, hints);
                int width = result.getWidth();
                int height = result.getHeight();
                int[] pixels = new int[width * height];
                // All are 0, or black, by default
                for (int y = 0; y < height; y++) {
                  int offset = y * width;
                  for (int x = 0; x < width; x++) {
                    pixels[offset + x] = result.get(x, y) ? BLACK : WHITE;
                  }
                }

                Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
                return bitmap;
              }

              private String guessAppropriateEncoding(CharSequence contents) {
                // Very crude at the moment
                for (int i = 0; i < contents.length(); i++) {
                  if (contents.charAt(i) > 0xFF) {
                    return "UTF-8";
                  }
                }
                return null;
              }

              private String trim(String s) {
                if (s == null) {
                  return null;
                }
                s = s.trim();
                return s.length() == 0 ? null : s;
              }

              private String escapeMECARD(String input) {
                if (input == null || (input.indexOf(':') < 0 && input.indexOf(';') < 0)) {
                  return input;
                }
                int length = input.length();
                StringBuilder result = new StringBuilder(length);
                for (int i = 0; i < length; i++) {
                  char c = input.charAt(i);
                  if (c == ':' || c == ';') {
                    result.append('\\');
                  }
                  result.append(c);
                }
                return result.toString();
              }
    }

来自this website (unedited)

的加密/解密类

以下是我的活动中onCreate()方法的片段:

QRCodeEncoder myQRCodeEncoder;

protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.qr_view);
            ImageView imageView = (ImageView)findViewById(R.id.qr_image);

            extStorageDirectory = Environment.getExternalStorageDirectory().toString();
            try
            {

                //JSON data is passed from another activity to this one
                qrMessage = getIntent().getStringExtra("QR_JSON");

                Intent encode = new Intent(Intents.Encode.ACTION);
                encode.putExtra(Intents.Encode.TYPE, Contents.Type.TEXT);
                encode.putExtra(Intents.Encode.FORMAT, "QR_CODE");
                    //This is the original plain text way that works:
                //encode.putExtra(Intents.Encode.DATA, qrMessage);

                    //This is the encyption way
                String encMessage = SimpleCrypto.encrypt("my s3cr3t k3y", qrMessage);
                encode.putExtra(Intents.Encode.DATA,encMessage);


                myQRCodeEncoder = new QRCodeEncoder(this, encode, 200);
            }
            catch(Exception e)
            {
                Toast.makeText(getApplicationContext(),"Could not encode:"+e.getMessage(),Toast.LENGTH_SHORT).show();
            }
            catch(Error e)
            {
                Toast.makeText(getApplicationContext(),"Could not encode:"+e.getMessage(),Toast.LENGTH_SHORT).show();
            }


            try {
                Bitmap qrBitmap = myQRCodeEncoder.encodeAsBitmap();
                imageView.setImageBitmap(qrBitmap);
            } catch (Exception e) {
                Toast.makeText(getApplicationContext(),"Could not set image:"+e.getMessage(),Toast.LENGTH_SHORT).show(); 
            }
    }

这是扫描仪的onActivityResult方法(我使用ZXing的条形码扫描仪来检索数据)

public void onActivityResult(int requestCode, int resultCode, Intent intent) {
    if (requestCode == 0) {
        if (resultCode == RESULT_OK) {
            String contents = intent.getStringExtra("SCAN_RESULT");//contents of the scan
            String format = intent.getStringExtra("SCAN_RESULT_FORMAT");
            // Handle successful scan

            /* display the scanned persons info*/
            try {
                String decryptedcontents = SimpleCrypto.decrypt("my s3cr3t k3y",contents);
                String result = getJSONFromScanData(decryptedcontents);

            } catch (Exception e) {
                // TODO Auto-generated catch block
                Toast.makeText(this, "Scanned data could not be decrypted:"+e.getMessage(), Toast.LENGTH_SHORT).show();//says 'pad block corrupted' as the message
            }



        } else if (resultCode == RESULT_CANCELED) {
            // Handle cancel
            Toast.makeText(this, "Scan cancelled", Toast.LENGTH_SHORT).show();
        }
    }
}
编辑:经过一些进一步调查后,似乎加密/解除过程似乎“削减”部分数据:

 JSONObject example = new JSONObject("{\"user_firstname\":\"Ben\",\"user_lastname\":\" Ten\",\"user_login\":\"benten\",\"user_pass\":\"password\",\"user_email\":\"benten@domain.com\"}");
                String mess = SimpleCrypto.encrypt("my s3cr3t k3y",example.toString());
String decrmess = SimpleCrypto.decrypt("my s3cr3t k3y",mess));
//decypts as:{"user_pass":"password","user_email":"benten@domain.com","user_login":"benten","user_lastname":"

你可以看到只有96个字符被解密,没有user_firstname或用户的实际姓氏,数据丢失,但这个数字不一致,我把user_email改为“benbenten@domain.com”,user_firstname改为“benben”和112个字符被解密了......我完全被难倒了

编辑2:YngveÅdlandsvik向我指出了正确的方向(非常感谢!),字符串长度需要是16的倍数,因此我将加密和解密方法中的Cipher.getInstance设置为:

Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding","BC");

并在我的主要活动中设置一个循环,在我的字符串末尾添加0作为加密前的自定义填充:

boolean carryOn = true;
                while(carryOn)
                {
                    int paddedLength = qrMessage.getBytes().length;
                    int checkMultiple16 = paddedLength%16;
                    if(checkMultiple16==0)
                    {
                        carryOn = false;
                    }
                    else
                    qrMessage+="0";
                }
编辑3:看起来QR编码仍然带有加密螺丝,我无法正确解密扫描的数据,看起来QR编码在编码到QR之前用字符串做了什么似乎打破了事情,猜猜我将不得不坚持QR中的未加密文本......

1 个答案:

答案 0 :(得分:1)

我没有仔细阅读过代码,但我认为这是因为AES只能同时运行16个字节的块。所以我的猜测是你需要在加密之前手动将某种形式的reversible padding应用到你的字符串中,这样它就会变成16的倍数,然后在解密后反转填充。

您还可以更改加密代码中的Cipher.getInstance()字符串,以便加密本身支持填充,但我不知道Android上有哪些填充类型和密码模式。