【问题标题】:Android - decryption issues with AES encrypted string in QR codeAndroid - QR码中AES加密字符串的解密问题
【发布时间】:2011-07-13 13:32:06
【问题描述】:

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

JSON 数据作为纯文本从 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() 方法的 sn-p:

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 中的未加密文本...

【问题讨论】:

    标签: android cryptography aes encryption


    【解决方案1】:

    我没有仔细阅读代码,但我认为这是因为 AES 一次只能对 16 个字节的块进行操作。所以我的猜测是您需要在加密之前手动将某种形式的reversible padding 应用于您的字符串,使其变为 16 的倍数,然后在解密后反转填充。

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

    【讨论】:

    • 这确实是建议!我必须将 Cipher.getInstance 设置为 Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding","BC"); 并且在我的主要活动中我有一个循环来检查我的字符串的字节长度是否是 16 的倍数,如果不是,则在末尾添加一个“0”字符串(我会用代码更新我原来的帖子)非常感谢这个:)
    • 如果可用,请尝试使用 PKCS#7 填充。建议不要使用欧洲央行。使用 CTR 或 CBC。
    • 我最初尝试使用 PKCS#7 填充但仍然截断了文本,我之前解释的方式似乎是唯一的选择......另外关于 ECB/CTR/CBC,你能解释它们之间的区别?当我将 ECB 更改为 CBC 时,出现 iv 为 0 的错误
    • ECB、CBC和CTR的区别见block Cypher modes。正如 Ranhiru 所说,不要使用欧洲央行。请改用 CBC 或 CTR。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-16
    • 1970-01-01
    • 2016-01-04
    • 2010-12-27
    相关资源
    最近更新 更多