我已经建立了一个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();
}
}
的加密/解密类
以下是我的活动中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中的未加密文本......
答案 0 :(得分:1)
我没有仔细阅读过代码,但我认为这是因为AES只能同时运行16个字节的块。所以我的猜测是你需要在加密之前手动将某种形式的reversible padding应用到你的字符串中,这样它就会变成16的倍数,然后在解密后反转填充。
您还可以更改加密代码中的Cipher.getInstance()字符串,以便加密本身支持填充,但我不知道Android上有哪些填充类型和密码模式。