Ich habe einen QR-Code-Generator und einen QR-Code-Scanner für die Weitergabe von Daten über Telefone und ihre Benutzer zwischen Telefonen gebaut (die Telefone werden ausgeliehen, so dass es ein Master-Telefon mit der Scanner-App und der Rest mit der Generator-App sein wird). Der QR-Code generiert ist ein JSON-Format-String mit einer Person Name/Nummer/Imei ihres Telefons, aber für die Sicherheit habe ich versucht, die Zeichenfolge vor der Codierung zu QR zu verschlüsseln, aber der gescannte QR-Code wirft ein "Pad-Block beschädigt" Fehler.
Die JSON-Daten kodiert in QR / dekodiert von QR fein als Klartext, und ich überprüfte die Verschlüsselung / Entschlüsselung vor der Verschlüsselung zu QR und die Daten verschlüsselt / entschlüsselt fein, so dass es etwas zu tun mit, wenn der verschlüsselte Text in QR kodiert wird, aber ich habe keine Ahnung, wo man damit anfangen!
Weiß jemand, wie ich das Problem beheben kann? Oder ob es irgendwelche QR-freundliche Verschlüsselungsmethoden gibt?!
Ich habe den QRCodeEncoder direkt aus ZXings Quellcode genommen und ihn in meine Aktivität eingefügt:
/**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();
}
}
Und die Verschlüsselungs-/Entschlüsselungsklasse von diese Website (unbearbeitet)
Hier ein Ausschnitt aus der onCreate()-Methode in meiner Aktivität:
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();
}
}
Und hier ist die onActivityResult-Methode des Scanners (ich verwende den Barcode-Scanner von ZXing, um die Daten abzurufen)
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();
}
}
}
EDIT: Nach weiteren Untersuchungen scheint es, dass der Verschlüsselungs-/Dekodierungsprozess einen Teil der Daten "abrasiert":
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":"
wie Sie sehen können, werden nur 96 Zeichen entschlüsselt, es gibt keinen user_firstname oder den tatsächlichen Nachnamen des Benutzers, die Daten fehlen, aber diese Zahl ist inkonsistent, ich änderte die user_email in "benbenten@domain.com" und den user_firstname in "benben" und 112 Zeichen wurden entschlüsselt... ich bin völlig ratlos
EDIT 2: Yngve Ådlandsvik hat mich freundlicherweise darauf hingewiesen (nochmals vielen Dank!), dass die Stringlänge ein Vielfaches von 16 sein muss, also habe ich die Cipher.getInstance sowohl in der Verschlüsselungs- als auch in der Entschlüsselungsmethode auf:
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding","BC");
und in meiner Hauptaktivität eine Schleife zum Hinzufügen von 0en am Ende meiner Zeichenfolge als benutzerdefinierte Auffüllung vor der Verschlüsselung festlegen:
boolean carryOn = true;
while(carryOn)
{
int paddedLength = qrMessage.getBytes().length;
int checkMultiple16 = paddedLength%16;
if(checkMultiple16==0)
{
carryOn = false;
}
else
qrMessage+="0";
}
EDIT 3: Es sieht so aus, als ob die QR-Kodierung immer noch die Verschlüsselung stört, ich kann die eingescannten Daten nicht richtig entschlüsseln, es sieht so aus, als ob die QR-Kodierung etwas mit Strings macht, bevor sie in QR kodiert, was die Sache zu zerstören scheint, ich schätze, ich muss bei unverschlüsseltem Text im QR bleiben...