diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460d8b..0000000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3787f8eff2..5cc2bcc062 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -72,7 +72,7 @@ dependencies { implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion" implementation 'com.nulab-inc:zxcvbn:1.2.3' - + //compile files('lib/sunjce_provider.jar') testImplementation "junit:junit:$rootProject.ext.junitVersion" testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion" diff --git a/app/lib/sunjce_provider.jar b/app/lib/sunjce_provider.jar new file mode 100755 index 0000000000..79facc3cf1 Binary files /dev/null and b/app/lib/sunjce_provider.jar differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4bf5b3a696..7f10e51de7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ + diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java index 82bc40a822..3edb1ae49d 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java @@ -19,6 +19,7 @@ import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import android.graphics.drawable.AnimationDrawable; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.TextInputLayout; @@ -35,6 +36,7 @@ import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.widget.Button; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Switch; import android.widget.TextView; @@ -71,6 +73,15 @@ public class GenerateFragment extends Fragment { private TextInputLayout etWalletViewKey; private TextInputLayout etWalletSpendKey; private TextInputLayout etWalletRestoreHeight; + + private LinearLayout llNfcPasswordSeed; + private TextInputLayout etNfcPasswordSeed; + private ImageView ivNfcPasswordSeed; + + private LinearLayout llNfcPasswordkey; + private TextInputLayout etNfcPasswordkey; + private ImageView ivNfcPasswordkey; + private Button bGenerate; private String type = null; @@ -92,8 +103,26 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, etWalletViewKey = (TextInputLayout) view.findViewById(R.id.etWalletViewKey); etWalletSpendKey = (TextInputLayout) view.findViewById(R.id.etWalletSpendKey); etWalletRestoreHeight = (TextInputLayout) view.findViewById(R.id.etWalletRestoreHeight); + + llNfcPasswordSeed = (LinearLayout) view.findViewById(R.id.llNfcPasswordSeed); + etNfcPasswordSeed = (TextInputLayout) view.findViewById(R.id.etNfcPasswordSeed); + ivNfcPasswordSeed = (ImageView) view.findViewById(R.id.ivNfcPasswordSeed); + llNfcPasswordkey = (LinearLayout) view.findViewById(R.id.llNfcPasswordkey); + etNfcPasswordkey = (TextInputLayout) view.findViewById(R.id.etNfcPasswordkey); + ivNfcPasswordkey = (ImageView) view.findViewById(R.id.ivNfcPasswordkey); + bGenerate = (Button) view.findViewById(R.id.bGenerate); + ivNfcPasswordkey.setImageResource(R.drawable.nfc_signal); + ivNfcPasswordSeed.setImageResource(R.drawable.nfc_signal); + AnimationDrawable nfcAnimationkey = (AnimationDrawable) ivNfcPasswordkey.getDrawable(); + nfcAnimationkey.setOneShot(false); + nfcAnimationkey.start(); + + AnimationDrawable nfcAnimationSeed = (AnimationDrawable) ivNfcPasswordSeed.getDrawable(); + nfcAnimationSeed.setOneShot(false); + nfcAnimationSeed.start(); + etWalletMnemonic.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT); etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etWalletViewKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); @@ -212,6 +241,8 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { return false; } }); + llNfcPasswordSeed.setVisibility(View.VISIBLE); + } else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) { etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { @@ -252,6 +283,8 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { return false; } }); + + llNfcPasswordkey.setVisibility(View.VISIBLE); } if (type.equals(TYPE_KEY)) { etWalletSpendKey.setVisibility(View.VISIBLE); @@ -268,6 +301,7 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { return false; } }); + llNfcPasswordkey.setVisibility(View.VISIBLE); } if (!type.equals(TYPE_NEW)) { etWalletRestoreHeight.setVisibility(View.VISIBLE); @@ -281,6 +315,7 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { return false; } }); + } bGenerate.setOnClickListener(new View.OnClickListener() diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java index cabcb83088..3c436df1af 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java @@ -562,4 +562,4 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { return openDialog; } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java index b4473cad1d..dae830567b 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java @@ -18,13 +18,20 @@ import android.app.Activity; import android.app.AlertDialog; +import android.app.PendingIntent; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.media.MediaScannerConnection; +import android.nfc.NfcAdapter; +import android.nfc.tech.NfcA; +import android.nfc.tech.NfcB; +import android.nfc.tech.NfcF; +import android.nfc.tech.NfcV; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; @@ -45,10 +52,14 @@ import com.m2049r.xmrwallet.dialog.AboutFragment; import com.m2049r.xmrwallet.dialog.CreditsFragment; import com.m2049r.xmrwallet.dialog.HelpFragment; +import com.m2049r.xmrwallet.dialog.InputNfcPasswordFragment; import com.m2049r.xmrwallet.dialog.PrivacyFragment; import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; +import com.m2049r.xmrwallet.nfc.AuthenticationException; +import com.m2049r.xmrwallet.nfc.TagUtil; +import com.m2049r.xmrwallet.nfc.ThreeDES; import com.m2049r.xmrwallet.service.WalletService; import com.m2049r.xmrwallet.util.FingerprintHelper; import com.m2049r.xmrwallet.util.Helper; @@ -58,6 +69,7 @@ import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.net.Socket; @@ -78,6 +90,16 @@ public class LoginActivity extends SecureActivity private Toolbar toolbar; + private TagUtil tagUtil; + + private InputNfcPasswordFragment inputNfcPassword; + + protected NfcAdapter nfcAdapter = null; + protected PendingIntent pendingIntent = null; + protected IntentFilter[] mFilters = null; + protected String[][] mTechLists = new String[][]{new String[]{NfcA.class.getName()}, new String[]{NfcF.class.getName()}, + new String[]{NfcB.class.getName()}, new String[]{NfcV.class.getName()}}; + @Override public void setToolbarButton(int type) { toolbar.setButton(type); @@ -136,6 +158,8 @@ public void onButton(int type) { } else { Timber.i("Waiting for permissions"); } + + initNfc(); } boolean checkServiceRunning() { @@ -319,7 +343,6 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { return false; } }); - dialog.show(); } @@ -349,6 +372,37 @@ protected void onPostExecute(Boolean result) { } } + private class AsyncBackupToNFC extends AsyncTask { + @Override + protected void onPreExecute() { + super.onPreExecute(); + + } + + @Override + protected Boolean doInBackground(Object... params) { + if (params.length != 2) return false; + Intent intent = (Intent) params[0]; + String walletName = (String) params[1]; + return backupWalletToNFC(intent, walletName); + } + + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + if (isDestroyed()) { + return; + } + dismissProgressDialog(); + if (!result) { + Toast.makeText(LoginActivity.this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(LoginActivity.this, "backup to NFC success ", Toast.LENGTH_LONG).show(); + inputNfcPassword.dismiss(); + } + } + } + private boolean backupWallet(String walletName) { File backupFolder = new File(getStorageRoot(), "backups"); if (!backupFolder.exists()) { @@ -370,12 +424,105 @@ private boolean backupWallet(String walletName) { return success; } + private boolean backupWalletToNFC(Intent intent, String walletName) { + boolean success = false; + String seed; + String spendKey; + String viewKey; + String address; + String height; + + File walletFile = Helper.getWalletFile(LoginActivity.this, walletName); + String realPassword = KeyStoreHelper.getCrazyPass(this, this.inputNfcPassword.getPassword()); + Wallet newWallet = WalletManager.getInstance().openWallet(walletFile.getPath(), realPassword); + Wallet.Status status = newWallet.getStatus(); + if (status != Wallet.Status.Status_Ok) { + Timber.e(newWallet.getErrorString()); + newWallet.close(); + return false; + } + seed = newWallet.getSeed();//length about 200 + spendKey = newWallet.getSecretSpendKey(); //length=64 + viewKey = newWallet.getSecretViewKey(); //length=64 + address = newWallet.getAddress(); //length=95 + height = new Long(newWallet.getBlockChainHeight()).toString(); + + boolean authenticated = false; + try { + tagUtil = TagUtil.selectTag(intent, false); + authenticated = tagUtil.authentication(intent, getKey(), false); + } catch (Exception e) { + e.printStackTrace(); + } + + if (authenticated) { + Toast toast = Toast.makeText(LoginActivity.this, "Writting keys now, please don't move the NFC", Toast.LENGTH_LONG); + try { + byte[] bytes = seed.getBytes("utf-8");//TODO: other character encoding + tagUtil.writePages(intent, (byte) 4, address.getBytes(), false);//write address from page 4 + tagUtil.writePages(intent, (byte) 30, viewKey.getBytes(), false);//write view key from page 30 + tagUtil.writePages(intent, (byte) 60, spendKey.getBytes(), false);//write spend key from page 60 + tagUtil.writePages(intent, (byte) 90, height.getBytes("utf-8"), false);//write height from page 90 + tagUtil.writePages(intent, (byte) 100, seed.getBytes("utf-8"), false);//write seed from page 100 + tagUtil.setAccess(intent, (byte) 30, (byte) 0x80, false);//pages after 30 cannot be read/write without a key! + success = true; + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + + } + return success; + } + + private byte[] getKey() { + return ThreeDES.defaultKey; + } + + private class AsyncBackupT extends AsyncTask { + @Override + protected void onPreExecute() { + super.onPreExecute(); + showProgressDialog(R.string.backup_progress); + } + + @Override + protected Boolean doInBackground(String... params) { + if (params.length != 1) return false; + return backupWallet(params[0]); + } + + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + if (isDestroyed()) { + return; + } + dismissProgressDialog(); + if (!result) { + Toast.makeText(LoginActivity.this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show(); + } + } + } + @Override - public void onWalletBackup(String walletName) { + public void onWalletBackupToFile(String walletName) { Timber.d("backup for wallet ." + walletName + "."); new AsyncBackup().execute(walletName); } + @Override + public void onWalletBackupToNFC(String walletName) { + Timber.d("backup to NFC hard wallet for wallet ." + walletName + "."); + InputNfcPasswordFragment.display(getSupportFragmentManager()); + inputNfcPassword = InputNfcPasswordFragment.getInstance(); + inputNfcPassword.setWalletName(walletName); + + } + private class AsyncArchive extends AsyncTask { @Override protected void onPreExecute() { @@ -508,6 +655,7 @@ public void showNet() { protected void onPause() { Timber.d("onPause()"); super.onPause(); + nfcAdapter.disableForegroundDispatch(this); } ProgressDialog progressDialog = null; @@ -555,6 +703,21 @@ protected void onResume() { // and show a progress dialog, but only if there isn't one already new AsyncWaitForService().execute(); } + nfcAdapter.enableForegroundDispatch(this, pendingIntent, mFilters, mTechLists); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()) + || NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction()) + || NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { + try { + new AsyncBackupToNFC().execute(intent, inputNfcPassword.getWalletName()); + } catch (Exception e) { + e.printStackTrace(); + } + } } private class MyProgressDialog extends ProgressDialog { @@ -1108,4 +1271,16 @@ public void action(String walletName, String password, boolean fingerprintUsed) Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show(); } } + + protected void initNfc() { + nfcAdapter = NfcAdapter.getDefaultAdapter(this); + //ifNFCSupport(); + // 将被调用的Intent,用于重复被Intent触发后将要执行的跳转 + pendingIntent = PendingIntent.getActivity(this, 0, + new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); + mTechLists = new String[][]{new String[]{NfcA.class.getName()}, new String[]{NfcF.class.getName()}, + new String[]{NfcB.class.getName()}, new String[]{NfcV.class.getName()}};// 允许扫描的标签类型 + + + } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java index 881e8e33fb..4dff440174 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java @@ -95,7 +95,9 @@ public interface Listener { void onWalletRename(String name); - void onWalletBackup(String name); + void onWalletBackupToFile(String name); + + void onWalletBackupToNFC(String name); void onWalletArchive(String walletName); @@ -257,8 +259,14 @@ public boolean onContextInteraction(MenuItem item, WalletManager.WalletInfo list case R.id.action_rename: activityCallback.onWalletRename(listItem.name); break; - case R.id.action_backup: + /*case R.id.action_backup: activityCallback.onWalletBackup(listItem.name); + break;*/ + case R.id.action_backup_file: + activityCallback.onWalletBackupToFile(listItem.name); + break; + case R.id.action_backup_nfc: //backup to NFC + activityCallback.onWalletBackupToNFC(listItem.name); break; case R.id.action_archive: activityCallback.onWalletArchive(listItem.name); diff --git a/app/src/main/java/com/m2049r/xmrwallet/dialog/InputNfcPasswordFragment.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/InputNfcPasswordFragment.java new file mode 100644 index 0000000000..d031cbacfb --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/dialog/InputNfcPasswordFragment.java @@ -0,0 +1,128 @@ +package com.m2049r.xmrwallet.dialog; + + +import android.app.AlertDialog; +import android.app.Dialog; +import android.graphics.drawable.AnimationDrawable; +import android.os.Bundle; +import android.support.design.widget.TextInputLayout; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; + +import com.m2049r.xmrwallet.R; +import com.nulabinc.zxcvbn.Strength; +import com.nulabinc.zxcvbn.Zxcvbn; + +import android.nfc.*; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.tech.*; +import android.nfc.tech.Ndef; +import android.widget.Toast; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentFilter; + +public class InputNfcPasswordFragment extends DialogFragment { + + private String walletName = null; + + + static final String TAG = "InputNfcPasswordFragment"; + + private TextInputLayout etWalletPassword; + + private static InputNfcPasswordFragment fragment = null; + + public static InputNfcPasswordFragment getInstance() { + if (fragment == null) { + fragment = new InputNfcPasswordFragment(); + } + + return fragment; + } + + public static void display(FragmentManager fm) { + FragmentTransaction ft = fm.beginTransaction(); + Fragment prev = fm.findFragmentByTag(TAG); + if (prev != null) { + ft.remove(prev); + } + InputNfcPasswordFragment.getInstance().show(ft, TAG); + } + + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_input_nfc_password, null); + etWalletPassword = (TextInputLayout) view.findViewById(R.id.etWalletPassword); + etWalletPassword.getEditText().addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + checkPassword(); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + }); + etWalletPassword.requestFocus(); + ImageView nfcAnimationImage = (ImageView) view.findViewById(R.id.NfcAnimation); + nfcAnimationImage.setImageResource(R.drawable.nfc_signal); + AnimationDrawable nfcAnimation = (AnimationDrawable) nfcAnimationImage.getDrawable(); + nfcAnimation.setOneShot(false); + nfcAnimation.start(); + + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setView(view); + initZxcvbn(); + return builder.create(); + } + + Zxcvbn zxcvbn = new Zxcvbn(); + + // initialize zxcvbn engine in background thread + private void initZxcvbn() { + new Thread(new Runnable() { + @Override + public void run() { + zxcvbn.measure(""); + } + }).start(); + } + + private void checkPassword() { + String password = etWalletPassword.getEditText().getText().toString(); + if (!password.isEmpty()) { + } else { + etWalletPassword.setError(null); + } + } + + //get input nfc password + public String getPassword() { + String password = etWalletPassword.getEditText().getText().toString(); + return password; + } + + public String getWalletName() { + return walletName; + } + + public void setWalletName(String walletName) { + this.walletName = walletName; + } + +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/nfc/AuthenticationException.java b/app/src/main/java/com/m2049r/xmrwallet/nfc/AuthenticationException.java new file mode 100644 index 0000000000..b3485a01b3 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/nfc/AuthenticationException.java @@ -0,0 +1,17 @@ +package com.m2049r.xmrwallet.nfc; + +public class AuthenticationException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 101001L; + + public AuthenticationException(String info) { + super(info); + } + + public AuthenticationException(String info, Exception e) { + super(info, e); + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/nfc/CRC16.java b/app/src/main/java/com/m2049r/xmrwallet/nfc/CRC16.java new file mode 100644 index 0000000000..2aa6b87cf6 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/nfc/CRC16.java @@ -0,0 +1,115 @@ +package com.m2049r.xmrwallet.nfc; + +import java.math.BigInteger; + +/** + * ISO/IEC FCD 14443-3 + * Name : "CRC-A" Width : 16 Poly : 1021 Init : C6C6 RefIn : True RefOut : True XorOut : 0000 Check : BF05 + * for more reference: http://wg8.de/wg8n1496_17n3613_Ballot_FCD14443-3.pdf + */ + +public class CRC16 { + + private int width = 16; + private long poly = (new BigInteger("1021", 16)).longValue(); + private long initialValue = (new BigInteger("c6c6", 16)).longValue(); + private boolean refIn = true; + private boolean refOut = true; + private long xorOut = (new BigInteger("0", 16)).longValue(); + ; + private long[] table; + private long value = 0L; + private long length = 0L; + private long topBit; + private long maskAllBits; + private long maskHelp; + + public static void main(String[] args) { + try { + CRC16 test = new CRC16(); + String hexString = Long.toHexString(test.getCRC16(ThreeDES.defaultKey)); + byte[] reversedBytes = TagUtil.reverse(TagUtil.hexStringToBytes(hexString)); + System.out.println(TagUtil.bytesToHexString(reversedBytes)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public long getCRC16(byte[] bytes) { + init(); + exec(bytes, 0, bytes.length); + long result = this.value; + if (this.refIn != this.refOut) { + result = reverse(result, this.width); + } + return (result ^ this.xorOut) & this.maskAllBits; + } + + + private void init() { + this.topBit = 1L << this.width - 1; + this.maskAllBits = -1L >>> 64 - this.width; + this.maskHelp = this.maskAllBits >>> 8; + initTable(); + length = 0L; + this.value = this.initialValue; + if (this.refIn) { + this.value = reverse(this.value, this.width); + } + + } + + private void exec(byte[] bytes, int start, int length) { + for (int i = start; i < length + start; ++i) { + this.exec(bytes[i]); + } + } + + private void exec(byte var1) { + int var2; + if (this.refIn) { + var2 = (int) (this.value ^ (long) var1) & 255; + this.value >>>= 8; + this.value &= this.maskHelp; + } else { + var2 = (int) (this.value >>> this.width - 8 ^ (long) var1) & 255; + this.value <<= 8; + } + this.value ^= this.table[var2]; + ++length; + } + + + private void initTable() { + this.table = new long[256]; + for (int i = 0; i < 256; ++i) { + long var1 = (long) i; + if (refIn) { + var1 = reverse(var1, 8); + } + var1 <<= this.width - 8; + for (int j = 0; j < 8; ++j) { + boolean var3 = (var1 & this.topBit) != 0L; + var1 <<= 1; + if (var3) { + var1 ^= this.poly; + } + } + if (refIn) { + var1 = reverse(var1, this.width); + } + this.table[i] = var1 & this.maskAllBits; + } + } + + private static long reverse(long var, int width) { + long temp = 0L; + for (int i = 0; i < width; ++i) { + temp <<= 1; + temp |= var & 1L; + var >>>= 1; + } + return var << width | temp; + } + +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/nfc/TagUtil.java b/app/src/main/java/com/m2049r/xmrwallet/nfc/TagUtil.java new file mode 100644 index 0000000000..04e7c972ae --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/nfc/TagUtil.java @@ -0,0 +1,795 @@ +package com.m2049r.xmrwallet.nfc; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import android.content.Intent; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.NfcA; + + +/** + * It's an utility class for NFC related operations include Read/Write/Authentication/ChangeKey/Lock/ + */ +public class TagUtil { + + private static final int TAGUTIL_TYPE_ULTRALIGHT = 1; + private static final int TAGUTIL_TYPE_CLASSIC = 2; + private static final int TAGUTIL_NfcA = 3; + private static final byte PAGE_ADDR_AUTH0 = (byte) 241;//page no 241 + private static final byte PAGE_ADDR_AUTH1 = (byte) 242;//page no 242 + + private static String uid; + private static String finalPage; + + private int tagType; + + private byte[] ivDefault = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};// default iv + private byte[] random = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};// radmon number + + public byte[] getRandom() { + return random; + } + + public void setRandom(byte[] random) { + this.random = random; + } + + private boolean authorised = true; + + private static NfcA nfcA = null; + + public TagUtil(String u, int type) { + uid = u; + tagType = type; + } + + /** + * get the TagUtil obj, throw exception for not supported nfc tag. + * + * @param intent intent + * @param isCheckSUM: add a checksum flag at the end of the command (for some cellphone use MTK chip, we should set this param true). + * How to know mtk chip: by getprop() method + * if ro.mediatek.gemini_support=true + * then + * it is mtk + * @return TagUtil + * @throws Exception: will throw this exception for unsupported nfc tag + */ + public static TagUtil selectTag(Intent intent, boolean isCheckSUM) throws Exception { + String action = intent.getAction(); + int type = 0; + if (isSupportedAction(action)) { + // get TAG in the intent + Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + String[] tagTypes = tagFromIntent.getTechList(); + String tagType = null; + for (int i = 0; i < tagFromIntent.getTechList().length; i++) { + if (type > 0) + continue; + if (tagTypes != null && tagTypes.length > 0) { + tagType = tagFromIntent.getTechList()[i]; + } + if ("android.nfc.tech.NfcA".equals(tagType)) { + try { + getTagUID_NfcA(tagFromIntent, isCheckSUM); + type = TAGUTIL_NfcA; + + } catch (Exception ex) { + throw ex; + } + } else { + throw new Exception("unsupported action " + action + " only support ACTION_TECH_DISCOVERED or ACTION_TAG_DISCOVERED or ACTION_NDEF_DISCOVERED"); + } + } + tagFromIntent = null; + return new TagUtil(uid, type); + } + return null; + } + + /** + * read one page( four bytes in a page), + * + * @param intent intent + * @param addr: page address + * @param isCheckSum: + * @return 4 bytes array + * @throws AuthenticationException + * @throws Exception + */ + public byte[] readOnePage(Intent intent, byte addr, boolean isCheckSum) throws AuthenticationException, Exception { + if (tagType == TagUtil.TAGUTIL_NfcA) + return readOnePage_NfcA(intent, addr, isCheckSum); + else + return null; + } + + + private byte[] readOnePage_NfcA(Intent intent, byte addr, boolean isCheckSum) throws AuthenticationException, Exception { + String action = intent.getAction(); + byte[] result = null; + if (isSupportedAction(action)) { + try { + if (authorised) { + byte[] data0 = new byte[2]; + byte[] dataWithCheckSum = new byte[4]; + data0[0] = 0x30; + data0[1] = addr; + byte[] data1; + if (isCheckSum) { + byte[] checkSum = getCheckSum(data0); + dataWithCheckSum[0] = data0[0]; + dataWithCheckSum[1] = data0[1]; + dataWithCheckSum[2] = checkSum[0]; + dataWithCheckSum[3] = checkSum[1]; + data1 = nfcA.transceive(dataWithCheckSum);// read 4 pages one time + } else + data1 = nfcA.transceive(data0);// read 4 pages one time + + result = new byte[4]; + if (data1.length < 16) + throw new AuthenticationException("please authenticate first!"); + else + System.arraycopy(data1, 0, result, 0, 4);// get the first page + } else { + throw new AuthenticationException("Authenticate First!"); + } + } catch (Exception e) { + throw e; + } + } + return result; + } + + /** + * read four pages( 4 bytes in a page) + * + * @param intent intent + * @param addr: start page to read + * @param isCheckSum: + * @return 16 bytes array + * @throws Exception Exception + */ + public byte[] readFourPage(Intent intent, byte addr, boolean isCheckSum) throws Exception { + if (tagType == TagUtil.TAGUTIL_NfcA) + return readFourPage_NfcA(intent, addr, isCheckSum); + else + return null; + } + + + private byte[] readFourPage_NfcA(Intent intent, byte addr, boolean isCheckSum) throws Exception { + String action = intent.getAction(); + byte[] result = null; + if (isSupportedAction(action)) { + try { + if (authorised) { + byte[] data0 = new byte[2]; + byte[] dataWithCheckSum = new byte[4]; + data0[0] = 0x30; + data0[1] = addr; + byte[] data1; + if (isCheckSum) { + byte[] checkSum = getCheckSum(data0); + dataWithCheckSum[0] = data0[0]; + dataWithCheckSum[1] = data0[1]; + dataWithCheckSum[2] = checkSum[0]; + dataWithCheckSum[3] = checkSum[1]; + result = nfcA.transceive(dataWithCheckSum); + } else + result = nfcA.transceive(data0); + } else { + throw new AuthenticationException("please authenticate first!"); + } + } catch (Exception e) { + throw e; + } + } + return result; + } + + + /** + * @param intent intent + * @param startPageNO start page to write + * @param contents bytes to write + * @param isCheckSum add checksum at the end of command. + * @return true or false + * @throws Exception Exception + */ + public boolean writePages(Intent intent, int startPageNO, byte contents[], boolean isCheckSum) + throws Exception { + boolean res = false; + if (startPageNO < 4 || startPageNO > 239) + throw new AuthenticationException("page no should between [4,239]"); + try { + byte newByteArray[] = appendByteArray(contents); + int pageNum = newByteArray.length / 4; + byte array[] = new byte[4]; + for (int i = 0; i < pageNum; i++) { + array[0] = newByteArray[4 * i]; + array[1] = newByteArray[1 + 4 * i]; + array[2] = newByteArray[2 + 4 * i]; + array[3] = newByteArray[3 + 4 * i]; + try { + writeOnePage(intent, (startPageNO + i), array, false); + } catch (Exception e) { + throw new Exception((new StringBuilder()).append("write page ").append(startPageNO + i).append(" failed").toString()); + } + } + + res = true; + } catch (Exception e) { + e.printStackTrace(); + } + return res; + } + + public boolean writeOnePage(Intent intent, int addr, byte[] contents, boolean isCheckSum) throws Exception { + boolean result = false; + String action = intent.getAction(); + if (isSupportedAction(action)) { + + try { + if (authorised) { + if (contents != null && contents.length == 4) { + byte[] data2 = new byte[6]; + byte[] dataWithCheckSum = new byte[8]; + data2[0] = (byte) 0xA2; + data2[1] = (byte) addr; + data2[2] = contents[0]; + data2[3] = contents[1]; + data2[4] = contents[2]; + data2[5] = contents[3]; + byte[] data3; + if (isCheckSum) { + byte[] checkSum = getCheckSum(data2); + dataWithCheckSum[0] = data2[0]; + dataWithCheckSum[1] = data2[1]; + dataWithCheckSum[2] = data2[2]; + dataWithCheckSum[3] = data2[3]; + dataWithCheckSum[4] = data2[4]; + dataWithCheckSum[5] = data2[5]; + dataWithCheckSum[6] = checkSum[0]; + dataWithCheckSum[7] = checkSum[1]; + data3 = nfcA.transceive(dataWithCheckSum); + } else + data3 = nfcA.transceive(data2); + result = true; + } else { + throw new AuthenticationException("contents must be four bytes"); + } + } else { + throw new AuthenticationException("please authenticate first!"); + } + } catch (Exception e) { + throw e; + } + } + return result; + } + + private static boolean isSupportedAction(String action) { + return NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) || NfcAdapter.ACTION_TAG_DISCOVERED.equals(action) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action); + } + + /** + * authentication,key is byte array. + * + * @param intent intent + * @param key byte array, length=16 bytes + * @param isCheckSum add checksum or not + * @return true or false + * @throws Exception Exception + */ + public boolean authentication(Intent intent, byte[] key, boolean isCheckSum) throws Exception { + if (tagType == TagUtil.TAGUTIL_NfcA) { + return authentication_NfcA(intent, key, isCheckSum); + } else + throw new Exception("unknow tag Type" + tagType); + } + + + private boolean authentication_NfcA(Intent intent, byte[] binaryKey, boolean isCheckSum) { + boolean result = false; + String action = intent.getAction(); + if (isSupportedAction(action)) { + try { + byte[] data = new byte[24]; + System.arraycopy(binaryKey, 0, data, 0, 16); + System.arraycopy(binaryKey, 0, data, 16, 8); + accreditation(data, isCheckSum); + authorised = true; + return true; + + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } else { + return false; + } + } + + /** + * read some pages from the fourth page + * + * @param intent: + * @param pageNums: how many pages should read. + * @param isCheckSum: + * @return byte array + * @throws Exception Exception + */ + public byte[] readAllPages(Intent intent, int pageNums, boolean isCheckSum) throws Exception { + + if (tagType == TagUtil.TAGUTIL_NfcA) + return readAllPages_NfcA(intent, pageNums, isCheckSum); + + else + return null; + } + + + private byte[] readAllPages_NfcA(Intent intent, int pageNums, boolean isCheckSum) throws Exception { + int byteNum = pageNums * 4; // 4 bytes a page + byte[] result = new byte[byteNum]; + try { + if (authorised) { + for (int i = 0x04; i < byteNum / 4; i++) { + byte[] data0 = new byte[2]; + byte[] dataWithCheckSum = new byte[4]; + data0[0] = 0x30; + data0[1] = (byte) i; + + byte[] data1; + if (isCheckSum) { + byte[] checkSum = getCheckSum(data0); + dataWithCheckSum[0] = data0[0]; + dataWithCheckSum[1] = data0[1]; + dataWithCheckSum[2] = checkSum[0]; + dataWithCheckSum[3] = checkSum[1]; + data1 = nfcA.transceive(dataWithCheckSum); + } else + data1 = nfcA.transceive(data0); + if (data1.length >= 4) + System.arraycopy(data1, 0, result, 4 * i, 4);// get one page + else + throw new Exception("read the" + i + "th page failed! " + data1.length + "bytes was read"); + } + return result; + } else { + throw new AuthenticationException("please authenticate first!"); + } + } catch (Exception e) { + throw e; + } + } + + /** + * set read/write key + * @param intent + * @param newKey new key to set + * @param isCheckSum add a checksum byte at the end of the command + * @return + * @throws Exception + */ + public boolean writeNewKey(Intent intent, String newKey, boolean isCheckSum) throws Exception { + boolean result = false; + String action = intent.getAction(); + if (isSupportedAction(action)) { + try { + if(authorised){ + String dataString = newKey; + + byte[] dataX = hexStringToBytes(dataString); + byte[] dataY = new byte[16]; + for(int i=0;i<16;i++){ + dataY[i] = dataX[15-i]; + System.out.println("mi"+dataY[i]); + } + byte[] data1 = new byte[6]; + byte[] data1WithCheckSum= new byte[8]; + data1[0] = (byte) 0xA2; + data1[1] = (byte) 0xF5; + System.arraycopy(dataY, 8, data1, 2, 4); + + byte[] data2 = new byte[6]; + byte[] data2WithCheckSum= new byte[8]; + data2[0] = (byte) 0xA2; + data2[1] = (byte) 0xF6; + System.arraycopy(dataY, 12, data2, 2, 4); + + byte[] data3 = new byte[6]; + byte[] data3WithCheckSum= new byte[8]; + data3[0] = (byte) 0xA2; + data3[1] = (byte) 0xF7; + System.arraycopy(dataY, 0, data3, 2, 4); + + byte[] data4 = new byte[6]; + byte[] data4WithCheckSum= new byte[8]; + data4[0] = (byte) 0xA2; + data4[1] = (byte) 0xF8; + System.arraycopy(dataY, 4, data4, 2, 4); + + if(isCheckSum) + { + byte[] checkSum = getCheckSum(data1); + for(int i=0;i<6;i++) + data1WithCheckSum[i]=data1[i]; + data1WithCheckSum[6]=checkSum[0]; + data1WithCheckSum[7]=checkSum[1]; + nfcA.transceive(data1WithCheckSum);// read 4 pages date + } + else + nfcA.transceive(data1); + + if(isCheckSum) + { + byte[] checkSum = getCheckSum(data2); + for(int i=0;i<6;i++) + data2WithCheckSum[i]=data2[i]; + data2WithCheckSum[6]=checkSum[0]; + data2WithCheckSum[7]=checkSum[1]; + nfcA.transceive(data2WithCheckSum); + } + else + nfcA.transceive(data2); + + if(isCheckSum) + { + byte[] checkSum = getCheckSum(data3); + for(int i=0;i<6;i++) + data3WithCheckSum[i]=data3[i]; + data3WithCheckSum[6]=checkSum[0]; + data3WithCheckSum[7]=checkSum[1]; + nfcA.transceive(data3WithCheckSum); + } + else + nfcA.transceive(data3); + + if(isCheckSum) + { + byte[] checkSum = getCheckSum(data4); + for(int i=0;i<6;i++) + data4WithCheckSum[i]=data4[i]; + data4WithCheckSum[6]=checkSum[0]; + data4WithCheckSum[7]=checkSum[1]; + nfcA.transceive(data4WithCheckSum); + } + else + nfcA.transceive(data4); + result = true; + + }else{ + throw new AuthenticationException("please authenticate first!"); + } + }catch (NumberFormatException e) { + throw new Exception("new key: "+newKey+" is not correct." +" key must be 32 hex chars"); + } catch (Exception e) { + throw e; + } + + } + return result; + } + + + /** + * set authenticated access level and the start address after which we need authenticaion for access. + * access level: 0 for read/write access,1 for write access + * + * @param intent intent + * @param startAddr: start address. + * @param operationType: 0x00 for write operation, 0x80 for read/write operation. + * @param isCheckSum: + * @return true or false + * @throws Exception Exception + */ + public boolean setAccess(Intent intent, byte startAddr, byte operationType, boolean isCheckSum) throws Exception { + if (tagType == TagUtil.TAGUTIL_NfcA) + return setAccess_NfcA(intent, startAddr, operationType, isCheckSum); + else + throw new Exception("unknow tag Type " + tagType); + } + + + private boolean setAccess_NfcA(Intent intent, byte startAddr, byte operationType, boolean isCheckSum) throws Exception { + + byte[] authAddrPage = this.readOnePage(intent, PAGE_ADDR_AUTH0, false); + byte[] opertionTypePage = this.readOnePage(intent, PAGE_ADDR_AUTH1, false); + authAddrPage[3] = startAddr; + opertionTypePage[0] = operationType; + boolean result1 = this.writeOnePage(intent, PAGE_ADDR_AUTH0, authAddrPage, isCheckSum); + boolean result2 = this.writeOnePage(intent, PAGE_ADDR_AUTH1, opertionTypePage, isCheckSum); + if (result1 && result2) + return true; + else + return false; + } + + /** + * lock the locking bit + * + * @param intent intent + * @param isCheckSum add checksum at the end of commands + * @return true or false + * @throws Exception Exception + */ + public boolean lockLockingbits(Intent intent, boolean isCheckSum) throws Exception { + if (tagType == TagUtil.TAGUTIL_NfcA) + return lockLockingbits_NfcA(intent, isCheckSum); + else + throw new Exception("unknow tag Type" + tagType + ". or SelectTag first."); + } + + private boolean lockLockingbits_NfcA(Intent intent, boolean isCheckSum) throws Exception { + byte[] contents1 = new byte[4]; + contents1[0] = (byte) 0; + contents1[1] = (byte) 0; + contents1[2] = (byte) 7; + contents1[3] = (byte) 0; + + byte[] contents2 = new byte[4]; + contents2[0] = (byte) 17; + contents2[1] = (byte) 15; + contents2[2] = (byte) 0; + contents2[3] = (byte) 0; + + if (writeOnePage(intent, (byte) 2, contents1, isCheckSum) && writeOnePage(intent, (byte) 40, contents2, isCheckSum)) + return true; + else + return false; + } + + private byte[] getBytesArray(int value) { + byte[] contents = new byte[4]; + contents[0] = (byte) (value >>> 24); + contents[1] = (byte) (value >>> 16); + contents[2] = (byte) (value >>> 8); + contents[3] = (byte) (value); + return contents; + } + + + public int getTagType() { + return tagType; + } + + private void accreditation(byte[] secretKeys, boolean isCheckSum) throws Exception { + byte[] iv = ivDefault; + + byte[] command0 = new byte[2];// first command + byte[] command0WithCheckSum = new byte[4];// first command(with check sum) + + byte[] command1;// first response + byte[] command1WithCheckSum = null;// first response with check sum + + byte[] command2;// command1 without first byte. + + byte[] command3;// decode command1 + byte[] command4;// command2 encode + byte[] command5;// command3 left move + byte[] command6;// RNDB (command5 and command4 encode) + byte[] command7; + byte[] command8; + byte[] command9; + byte[] command10; + byte[] command11; + + command0[0] = (byte) 0x1A; // command + command0[1] = (byte) 0x00; // flag + if (isCheckSum) { + byte[] checkSum = getCheckSum(command0); + command0WithCheckSum[0] = command0[0]; + command0WithCheckSum[1] = command0[1]; + command0WithCheckSum[2] = checkSum[0]; + command0WithCheckSum[3] = checkSum[1]; + command1WithCheckSum = nfcA.transceive(command0WithCheckSum);// 11 bytes + if (command1WithCheckSum.length != 11) { + String str = ""; + for (int i = 0; i < command1WithCheckSum.length; i++) { + str = str + " byte" + i + "=" + command1WithCheckSum[i] + " "; + } + throw new Exception("length of response is not 11 bytes. the response bytes is: " + str); + } + command1 = new byte[9]; + System.arraycopy(command1WithCheckSum, 0, command1, 0, 9); + } else { + command1 = nfcA.transceive(command0); + } + command2 = new byte[8]; + if (command1.length != 9) { + String str = ""; + for (int i = 0; i < command1.length; i++) { + str = str + " byte" + i + "=" + command1[i] + " "; + } + throw new Exception("length of response is not 9 bytes. the response bytes is: " + str); + } + System.arraycopy(command1, 1, command2, 0, 8); + command3 = ThreeDES.decode(command2, iv, secretKeys); + iv = command2; + command4 = ThreeDES.encode(random, iv, secretKeys); + iv = command4; + command5 = new byte[8]; + System.arraycopy(command3, 1, command5, 0, 7); + command5[7] = command3[0]; + command6 = ThreeDES.encode(command5, iv, secretKeys); + + command7 = new byte[16]; + System.arraycopy(command4, 0, command7, 0, 8); + System.arraycopy(command6, 0, command7, 8, 8); + command8 = new byte[17]; + + command8[0] = (byte) 0xAF; + System.arraycopy(command7, 0, command8, 1, 16); + + if (isCheckSum) { + byte[] command8WithCheckSum = new byte[19]; + byte[] checkSum = getCheckSum(command8); + System.arraycopy(command8, 0, command8WithCheckSum, 0, 17); + command8WithCheckSum[17] = checkSum[0]; + command8WithCheckSum[18] = checkSum[1]; + command9 = nfcA.transceive(command8WithCheckSum); + } else { + command9 = nfcA.transceive(command8); + } + command10 = new byte[8]; + System.arraycopy(command9, 1, command10, 0, 8); + iv = command6; + command11 = ThreeDES.decode(command10, iv, secretKeys); + } + + + public static byte[] hexStringToBytes(String hexString) { + if (hexString == null || hexString.equals("")) { + return null; + } + hexString = hexString.toUpperCase(); + int length = hexString.length() / 2; + char[] hexChars = hexString.toCharArray(); + byte[] d = new byte[length]; + for (int i = 0; i < length; i++) { + int pos = i * 2; + d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); + } + return d; + } + + private static byte charToByte(char c) { + int i = "0123456789ABCDEF".indexOf(c); + if (i == -1) { + throw new NumberFormatException(); + } else { + return (byte) i; + } + } + + private static String StringtoHexString(String str) { + String hexString = "0123456789ABCDEF"; + byte bytes[] = str.getBytes(); + StringBuilder sb = new StringBuilder(bytes.length * 2); + for (int i = 0; i < bytes.length; i++) { + sb.append(hexString.charAt((bytes[i] & 240) >> 4)); + sb.append(hexString.charAt((bytes[i] & 15) >> 0)); + } + + return sb.toString(); + } + + public static String hexStringToString(String bytes) { + String hexString = "0123456789ABCDEF"; + ByteArrayOutputStream baos = new ByteArrayOutputStream(bytes.length() / 2); + for (int i = 0; i < bytes.length(); i += 2) + baos.write(hexString.indexOf(bytes.charAt(i)) << 4 | hexString.indexOf(bytes.charAt(i + 1))); + + return new String(baos.toByteArray()); + } + + public static byte[] StringtoBytes(String str) { + String hexstr = StringtoHexString(str); + return hexStringToBytes(hexstr); + } + + private byte[] appendByteArray(byte byteArray[]) { + int length = byteArray.length; + int m = length % 4; + byte newByteArray[]; + if (m == 0) + newByteArray = new byte[length]; + else + newByteArray = new byte[length + (4 - m)]; + System.arraycopy(byteArray, 0, newByteArray, 0, length); + return newByteArray; + } + + + public static String bytesToHexString(byte[] src) { + StringBuilder stringBuilder = new StringBuilder(); + if (src == null || src.length <= 0) { + return null; + } + for (int i = 0; i < src.length; i++) { + int v = src[i] & 0xFF; + String hv = Integer.toHexString(v); + if (hv.length() < 2) { + stringBuilder.append(0); + } + stringBuilder.append(hv); + } + return stringBuilder.toString(); + } + + private static void getTagUID_NfcA(Tag tag, boolean isCheckSum) throws Exception { + nfcA = NfcA.get(tag); + try { + nfcA.connect(); + byte[] datas = new byte[2]; + byte[] datasWithCheckSum = new byte[4]; + datas[0] = 0x30; + datas[1] = 0x00; + byte[] datar; + if (isCheckSum) { + byte[] checkSum = getCheckSum(datas); + datasWithCheckSum[0] = datas[0]; + datasWithCheckSum[1] = datas[1]; + datasWithCheckSum[2] = checkSum[0]; + datasWithCheckSum[3] = checkSum[1]; + datar = nfcA.transceive(datasWithCheckSum); + } else + datar = nfcA.transceive(datas); + byte[] datau = new byte[7];//uid + System.arraycopy(datar, 0, datau, 0, 3); + System.arraycopy(datar, 4, datau, 3, 4); + uid = bytesToHexString(datau); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + + public static String getUid() { + return uid; + } + + + public int getAuthenticationAddr(Intent intent, boolean isCheckSum) throws Exception { + byte[] result = readOnePage(intent, PAGE_ADDR_AUTH0, isCheckSum); + return result[0]; + } + + public int getAuthenticationType(Intent intent, boolean isCheckSum) throws Exception { + byte[] result = readOnePage(intent, PAGE_ADDR_AUTH1, isCheckSum); + return result[0]; + } + + /** + * close the nfc connection + */ + public void close() { + try { + if (nfcA != null) + nfcA.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static byte[] getCheckSum(byte[] byteAyyay) { + CRC16 checksum = new CRC16(); + String hexString = Long.toHexString(checksum.getCRC16(byteAyyay)); + return reverse(hexStringToBytes(hexString)); + } + + + public static byte[] reverse(byte[] bytes) { + byte[] result = new byte[bytes.length]; + + for (int i = 0; i < bytes.length; i++) { + result[i] = bytes[bytes.length - i - 1]; + } + return result; + } + +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/nfc/ThreeDES.java b/app/src/main/java/com/m2049r/xmrwallet/nfc/ThreeDES.java new file mode 100644 index 0000000000..dea046ee0c --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/nfc/ThreeDES.java @@ -0,0 +1,55 @@ +package com.m2049r.xmrwallet.nfc; + + +import java.security.Key; +import java.security.Security; + +import javax.crypto.Cipher; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESedeKeySpec; +import javax.crypto.spec.IvParameterSpec; + +/** + * 3DES + */ +public class ThreeDES { + + //default key(KEY1+KEY2) for 3DES + public static byte[] defaultKey = {0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42 + , 0x21, 0x4E, 0x41, 0x43, 0x55, 0x4F, 0x59, 0x46}; + + + private final static byte[] iv = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + private final static String encoding = "utf-8"; + + static { + Security.addProvider(new com.sun.crypto.provider.SunJCE()); + } + + public static byte[] encode(byte[] plainText, byte[] ivs, byte[] secretKeys) throws Exception { + Key deskey = null; + DESedeKeySpec spec = new DESedeKeySpec(secretKeys); + SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("desede"); + deskey = keyfactory.generateSecret(spec); + + Cipher cipher = Cipher.getInstance("desede/CBC/NoPadding"); + IvParameterSpec ips = new IvParameterSpec(ivs); + cipher.init(Cipher.ENCRYPT_MODE, deskey, ips); + byte[] encryptData = cipher.doFinal(plainText); + + return encryptData; + } + + public static byte[] decode(byte[] encryptText, byte[] ivs, byte[] secretKeys) throws Exception { + Key deskey = null; + DESedeKeySpec spec = new DESedeKeySpec(secretKeys); + SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("desede"); + deskey = keyfactory.generateSecret(spec); + Cipher cipher = Cipher.getInstance("desede/CBC/NoPadding"); + IvParameterSpec ips = new IvParameterSpec(ivs); + cipher.init(Cipher.DECRYPT_MODE, deskey, ips); + byte[] decryptData = cipher.doFinal(encryptText); + + return decryptData; + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/nfc1.png b/app/src/main/res/drawable/nfc1.png new file mode 100644 index 0000000000..1cf6f9253f Binary files /dev/null and b/app/src/main/res/drawable/nfc1.png differ diff --git a/app/src/main/res/drawable/nfc2.png b/app/src/main/res/drawable/nfc2.png new file mode 100644 index 0000000000..f971f19e6b Binary files /dev/null and b/app/src/main/res/drawable/nfc2.png differ diff --git a/app/src/main/res/drawable/nfc3.png b/app/src/main/res/drawable/nfc3.png new file mode 100644 index 0000000000..50d8d85811 Binary files /dev/null and b/app/src/main/res/drawable/nfc3.png differ diff --git a/app/src/main/res/drawable/nfc_signal.xml b/app/src/main/res/drawable/nfc_signal.xml new file mode 100644 index 0000000000..aeb282c9a7 --- /dev/null +++ b/app/src/main/res/drawable/nfc_signal.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_generate.xml b/app/src/main/res/layout/fragment_generate.xml index 7a8df46466..9df86d946a 100644 --- a/app/src/main/res/layout/fragment_generate.xml +++ b/app/src/main/res/layout/fragment_generate.xml @@ -169,6 +169,73 @@ android:textAlignment="textStart" /> + + + + + + + + + + + + + + + + + + + + +