添加NFC钥匙概念,使用RSA秘钥管理WinBoLL服务登录验证模块。
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sun Jan 11 14:21:27 HKT 2026
|
||||
#Sun Jan 11 13:14:09 GMT 2026
|
||||
stageCount=4
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.15
|
||||
publishVersion=15.15.3
|
||||
buildCount=0
|
||||
buildCount=1
|
||||
baseBetaVersion=15.15.4
|
||||
|
||||
@@ -42,4 +42,4 @@
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
9
appbase/src/main/res/layout/activity_nfcrsaoperate.xml
Normal file
9
appbase/src/main/res/layout/activity_nfcrsaoperate.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sun Jan 11 14:21:09 HKT 2026
|
||||
#Sun Jan 11 13:14:09 GMT 2026
|
||||
stageCount=4
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.15
|
||||
publishVersion=15.15.3
|
||||
buildCount=0
|
||||
buildCount=1
|
||||
baseBetaVersion=15.15.4
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
|
||||
</activity>
|
||||
|
||||
<activity android:name="cc.winboll.studio.libappbase.activities.AboutActivity"/>
|
||||
<activity android:name="cc.winboll.studio.libappbase.activities.NfcRsaLoginActivity"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
package cc.winboll.studio.libappbase.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.Tag;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.R;
|
||||
import cc.winboll.studio.libappbase.utils.NfcRsaAuthTool;
|
||||
|
||||
/**
|
||||
* @Describe NFC RSA登录认证窗口
|
||||
* 核心逻辑:贴近NFC→有密钥显示保存按钮(存应用data区+内存缓存)→无密钥启用初始化按钮(生成私钥写NFC)
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/11 20:34:00
|
||||
* @LastEditTime 2026/01/12 16:28:00
|
||||
*/
|
||||
public class NfcRsaLoginActivity extends Activity implements View.OnClickListener {
|
||||
// 常量定义
|
||||
private static final String TAG = "NfcRsaLoginActivity";
|
||||
|
||||
// NFC核心相关属性
|
||||
private NfcAdapter mNfcAdapter;
|
||||
private PendingIntent mNfcPendingIntent;
|
||||
|
||||
// 视图控件相关属性
|
||||
private TextView mTvNfcState;
|
||||
private TextView mTvPrivateKey;
|
||||
private TextView mTvPublicKey;
|
||||
private Button mBtnOptKey; // 复用按钮:有密钥=保存本地,无密钥=初始化密钥
|
||||
|
||||
// 业务相关属性
|
||||
private NfcRsaAuthTool mNfcRsaAuthTool;
|
||||
private boolean isPreparingInit = false; // 标记是否准备初始化密钥(替代原写入标记)
|
||||
private String mTempPrivateKey; // 临时存储NFC读取的有效私钥,用于后续保存
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_nfc_rsa_operate);
|
||||
initView();
|
||||
initNfcTool();
|
||||
initNfcConfig();
|
||||
LogUtils.d(TAG, "onCreate: NFC RSA登录窗口初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视图控件,绑定点击事件,默认状态配置
|
||||
*/
|
||||
private void initView() {
|
||||
mTvNfcState = findViewById(R.id.tv_nfc_state);
|
||||
mTvPrivateKey = findViewById(R.id.tv_private_key);
|
||||
mTvPublicKey = findViewById(R.id.tv_public_key);
|
||||
mBtnOptKey = findViewById(R.id.btn_create_write_key);
|
||||
|
||||
mBtnOptKey.setOnClickListener(this);
|
||||
mBtnOptKey.setEnabled(false);
|
||||
mTvNfcState.setText("正在监听NFC卡片,请贴近设备检测密钥...");
|
||||
mTvPrivateKey.setText("私钥内容:无");
|
||||
mTvPublicKey.setText("公钥内容:无");
|
||||
LogUtils.d(TAG, "initView: 视图控件初始化完成,功能按钮默认禁用");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化核心工具类NfcRsaAuthTool,校验NFC基础可用性
|
||||
*/
|
||||
private void initNfcTool() {
|
||||
mNfcRsaAuthTool = NfcRsaAuthTool.getInstance(this);
|
||||
LogUtils.d(TAG, "initNfcTool: NfcRsaAuthTool单例获取完成");
|
||||
|
||||
if (!mNfcRsaAuthTool.isNfcAvailable()) {
|
||||
mTvNfcState.setText("❌ 设备不支持NFC或未开启NFC");
|
||||
mBtnOptKey.setEnabled(false);
|
||||
Toast.makeText(this, "请先在设置中开启NFC功能", Toast.LENGTH_LONG).show();
|
||||
LogUtils.w(TAG, "initNfcTool: NFC不可用,设备不支持或未开启");
|
||||
} else {
|
||||
LogUtils.d(TAG, "initNfcTool: NFC基础可用性校验通过");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化NFC前台监听配置,页面打开即生效,适配API30
|
||||
*/
|
||||
private void initNfcConfig() {
|
||||
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||
mNfcPendingIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
LogUtils.d(TAG, "initNfcConfig: NFC前台监听配置初始化完成,适配API30");
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心分发方法:处理NFC相关意图,区分 密钥检测/密钥初始化 逻辑
|
||||
* @param intent NFC触发的意图对象
|
||||
*/
|
||||
private void handleNfcIntent(Intent intent) {
|
||||
if (mNfcRsaAuthTool == null || !mNfcRsaAuthTool.isNfcAvailable()) {
|
||||
LogUtils.w(TAG, "handleNfcIntent: NFC工具类为空或NFC不可用,跳过意图处理");
|
||||
return;
|
||||
}
|
||||
|
||||
String action = intent.getAction();
|
||||
LogUtils.d(TAG, "handleNfcIntent: 收到NFC意图,action=" + action);
|
||||
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)
|
||||
|| NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)
|
||||
|| NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
|
||||
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
||||
if (tag != null) {
|
||||
LogUtils.d(TAG, "handleNfcIntent: 成功提取NFC Tag对象,当前初始化准备状态=" + isPreparingInit);
|
||||
if (isPreparingInit) {
|
||||
createWriteAndValidateKey(tag); // 准备初始化:生成+写NFC
|
||||
} else {
|
||||
readAndValidateKey(tag); // 正常状态:检测NFC密钥
|
||||
}
|
||||
} else {
|
||||
LogUtils.w(TAG, "handleNfcIntent: NFC意图中未提取到有效Tag对象");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取NFC中私钥,执行有效性校验,区分场景更新UI(有有效密钥显保存按钮,无则显初始化按钮)
|
||||
* @param tag NFC卡片Tag对象
|
||||
*/
|
||||
private void readAndValidateKey(final Tag tag) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LogUtils.d(TAG, "readAndValidateKey: 子线程读取NFC私钥,Tag=" + tag);
|
||||
final String privateKeyStr = mNfcRsaAuthTool.readPrivateKeyFromNfc(tag);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (privateKeyStr != null && !privateKeyStr.isEmpty()) {
|
||||
// NFC读取到私钥,校验有效性
|
||||
boolean priValid = mNfcRsaAuthTool.validatePrivateKey(privateKeyStr);
|
||||
String publicKeyStr = mNfcRsaAuthTool.getCachePublicKeyStr();
|
||||
boolean pubValid = mNfcRsaAuthTool.validatePublicKey(privateKeyStr, publicKeyStr);
|
||||
|
||||
LogUtils.d(TAG, "readAndValidateKey: 私钥读取完成,有效性=" + priValid + ",公钥提取有效性=" + pubValid);
|
||||
if (priValid) {
|
||||
mTempPrivateKey = privateKeyStr; // 缓存有效私钥,用于后续保存
|
||||
mTvNfcState.setText("✅ NFC检测到有效密钥,点击按钮保存到本地");
|
||||
mBtnOptKey.setText("保存密钥到应用本地并缓存");
|
||||
} else {
|
||||
mTvNfcState.setText("⚠️ NFC私钥无效,点击按钮重新初始化");
|
||||
mBtnOptKey.setText("初始化RSA密钥写入NFC");
|
||||
mTempPrivateKey = null;
|
||||
}
|
||||
mTvPrivateKey.setText("私钥内容:\n" + privateKeyStr);
|
||||
mTvPublicKey.setText(publicKeyStr != null ? "公钥内容:\n" + publicKeyStr : "公钥内容:提取失败");
|
||||
mBtnOptKey.setEnabled(true);
|
||||
Toast.makeText(NfcRsaLoginActivity.this, priValid ? "检测到有效密钥" : "密钥无效,请初始化", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
// NFC无有效私钥,显示初始化按钮
|
||||
LogUtils.w(TAG, "readAndValidateKey: NFC中未读取到有效私钥");
|
||||
mTvNfcState.setText("❌ NFC无有效RSA私钥,点击按钮初始化");
|
||||
mTvPrivateKey.setText("私钥内容:无");
|
||||
mTvPublicKey.setText("公钥内容:无");
|
||||
mBtnOptKey.setText("初始化RSA密钥写入NFC");
|
||||
mBtnOptKey.setEnabled(true);
|
||||
mTempPrivateKey = null;
|
||||
Toast.makeText(NfcRsaLoginActivity.this, "未检测到有效私钥", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
isPreparingInit = false; // 重置初始化标记
|
||||
LogUtils.d(TAG, "readAndValidateKey: 私钥检测流程结束,重置初始化准备状态");
|
||||
}
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成RSA私钥、写入NFC、执行密钥有效性校验并更新UI(初始化密钥核心逻辑)
|
||||
* @param tag NFC卡片Tag对象
|
||||
*/
|
||||
private void createWriteAndValidateKey(final Tag tag) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LogUtils.d(TAG, "createWriteAndValidateKey: 开始创建私钥并写入NFC,Tag=" + tag);
|
||||
// 1. 生成RSA私钥
|
||||
final String privateKeyStr = mNfcRsaAuthTool.generateRsaPrivateKey();
|
||||
if (privateKeyStr == null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mTvNfcState.setText("❌ 私钥生成失败");
|
||||
Toast.makeText(NfcRsaLoginActivity.this, "私钥生成失败,请重试", Toast.LENGTH_SHORT).show();
|
||||
isPreparingInit = false;
|
||||
mBtnOptKey.setEnabled(true);
|
||||
}
|
||||
});
|
||||
LogUtils.e(TAG, "createWriteAndValidateKey: RSA私钥生成失败");
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, "createWriteAndValidateKey: RSA私钥生成成功");
|
||||
|
||||
// 2. 写入NFC卡片
|
||||
final boolean writeSuccess = mNfcRsaAuthTool.writePrivateKeyToNfc(tag, privateKeyStr);
|
||||
if (!writeSuccess) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mTvNfcState.setText("❌ 私钥写入NFC失败");
|
||||
Toast.makeText(NfcRsaLoginActivity.this, "私钥写入失败,请重试", Toast.LENGTH_SHORT).show();
|
||||
isPreparingInit = false;
|
||||
mBtnOptKey.setEnabled(true);
|
||||
}
|
||||
});
|
||||
LogUtils.e(TAG, "createWriteAndValidateKey: 私钥写入NFC失败");
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, "createWriteAndValidateKey: 私钥写入NFC成功");
|
||||
|
||||
// 3. 提取公钥并双重校验有效性
|
||||
final String publicKeyStr = mNfcRsaAuthTool.extractPublicKeyFromPrivateKeyStr(privateKeyStr);
|
||||
final boolean priValid = mNfcRsaAuthTool.validatePrivateKey(privateKeyStr);
|
||||
final boolean pubValid = mNfcRsaAuthTool.validatePublicKey(privateKeyStr, publicKeyStr);
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LogUtils.d(TAG, "createWriteAndValidateKey: 密钥校验完成,私钥有效=" + priValid + ",公钥有效=" + pubValid);
|
||||
if (priValid && pubValid) {
|
||||
mTvNfcState.setText("✅ 密钥初始化成功,已写入NFC");
|
||||
mTvPrivateKey.setText("私钥内容:\n" + privateKeyStr);
|
||||
mTvPublicKey.setText(publicKeyStr != null ? "公钥内容:\n" + publicKeyStr : "公钥内容:提取失败");
|
||||
Toast.makeText(NfcRsaLoginActivity.this, "密钥创建写入成功,可贴近NFC保存本地", Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
mTvNfcState.setText("⚠️ 写入成功,但密钥校验失败");
|
||||
Toast.makeText(NfcRsaLoginActivity.this, "写入成功但密钥无效,请重新操作", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
mBtnOptKey.setText("初始化RSA密钥写入NFC");
|
||||
mBtnOptKey.setEnabled(true);
|
||||
isPreparingInit = false;
|
||||
mTempPrivateKey = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存有效私钥到应用data区,同时缓存到工具类内存属性
|
||||
*/
|
||||
private void saveKeyToLocalAndCache() {
|
||||
LogUtils.d(TAG, "saveKeyToLocalAndCache: 开始执行密钥本地保存+内存缓存");
|
||||
if (mTempPrivateKey == null || mTempPrivateKey.isEmpty()) {
|
||||
Toast.makeText(this, "无有效密钥可保存", Toast.LENGTH_SHORT).show();
|
||||
LogUtils.w(TAG, "saveKeyToLocalAndCache: 临时有效私钥为空,保存失败");
|
||||
return;
|
||||
}
|
||||
boolean saveSuccess = mNfcRsaAuthTool.savePrivateKeyToLocal(mTempPrivateKey);
|
||||
if (saveSuccess) {
|
||||
mTvNfcState.setText("✅ 密钥已保存到应用本地,登录完成");
|
||||
mTvPrivateKey.setText("私钥内容:\n" + mNfcRsaAuthTool.getCachePrivateKeyStr());
|
||||
mTvPublicKey.setText("公钥内容:\n" + mNfcRsaAuthTool.getCachePublicKeyStr());
|
||||
mBtnOptKey.setEnabled(false);
|
||||
Toast.makeText(this, "密钥保存成功,已缓存到内存", Toast.LENGTH_LONG).show();
|
||||
LogUtils.d(TAG, "saveKeyToLocalAndCache: 密钥本地存储+工具类内存缓存成功");
|
||||
} else {
|
||||
Toast.makeText(this, "密钥保存到本地失败", Toast.LENGTH_SHORT).show();
|
||||
LogUtils.e(TAG, "saveKeyToLocalAndCache: 密钥本地保存失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.btn_create_write_key) {
|
||||
LogUtils.d(TAG, "onClick: 点击功能按钮,当前按钮文本=" + mBtnOptKey.getText().toString());
|
||||
if (!mNfcRsaAuthTool.isNfcAvailable()) {
|
||||
Toast.makeText(this, "NFC不可用,无法执行操作", Toast.LENGTH_SHORT).show();
|
||||
LogUtils.w(TAG, "onClick: NFC不可用,拒绝按钮操作");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mBtnOptKey.getText().toString().contains("保存")) {
|
||||
// 按钮为保存功能:直接保存本地+缓存
|
||||
saveKeyToLocalAndCache();
|
||||
} else {
|
||||
// 按钮为初始化功能:进入准备状态,等待贴近NFC
|
||||
isPreparingInit = true;
|
||||
mTvNfcState.setText("请贴近NFC卡片,执行密钥写入...");
|
||||
mBtnOptKey.setEnabled(false);
|
||||
Toast.makeText(this, "请贴近NFC卡片完成密钥初始化", Toast.LENGTH_SHORT).show();
|
||||
LogUtils.d(TAG, "onClick: 已进入密钥初始化准备状态,等待NFC贴近");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (mNfcAdapter != null && mNfcRsaAuthTool.isNfcAvailable()) {
|
||||
mNfcAdapter.enableForegroundDispatch(this, mNfcPendingIntent, null, null);
|
||||
LogUtils.d(TAG, "onResume: NFC前台监听已启用");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (mNfcAdapter != null) {
|
||||
mNfcAdapter.disableForegroundDispatch(this);
|
||||
LogUtils.d(TAG, "onPause: NFC前台监听已禁用");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
LogUtils.d(TAG, "onNewIntent: 收到新NFC意图,分发处理");
|
||||
handleNfcIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,480 @@
|
||||
package cc.winboll.studio.libappbase.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.Tag;
|
||||
import android.nfc.tech.Ndef;
|
||||
import android.nfc.tech.NdefFormatable;
|
||||
import android.util.Base64;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.RSAPrivateCrtKeySpec;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
/**
|
||||
* @Describe NFC RSA认证工具类,单例模式
|
||||
* 核心功能:RSA密钥生成、NFC密钥读写、本地data区密钥存储、密钥有效性校验、内存密钥缓存
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/11 21:00:00
|
||||
* @LastEditTime 2026/01/12 17:46:00
|
||||
*/
|
||||
public class NfcRsaAuthTool {
|
||||
// 常量配置(集中管理,简洁无冗余)
|
||||
private static final String TAG = "NfcRsaAuthTool";
|
||||
private static final String RSA_ALGORITHM = "RSA";
|
||||
private static final int RSA_KEY_SIZE = 2048;
|
||||
private static final String NFC_KEY_TAG = "RSA_AUTH_PRIV_";
|
||||
private static final String CHARSET = "UTF-8";
|
||||
private static final String LOCAL_KEY_FILE_NAME = "rsa_auth_private.key";
|
||||
private static final String RSA_TEST_DATA = "NFC_RSA_AUTH_VALID";
|
||||
|
||||
// 单例实例(线程安全双重校验锁核心)
|
||||
private static volatile NfcRsaAuthTool sInstance;
|
||||
|
||||
// 核心属性(按用途排序,注释清晰)
|
||||
private Context mContext;
|
||||
private NfcAdapter mNfcAdapter;
|
||||
private String mCachePrivateKeyStr; // 内存缓存Base64私钥字符串
|
||||
private String mCachePublicKeyStr; // 内存缓存Base64公钥字符串
|
||||
|
||||
// 私有构造器(禁止外部实例化,绑定全局上下文)
|
||||
private NfcRsaAuthTool(Context context) {
|
||||
this.mContext = context.getApplicationContext();
|
||||
this.mNfcAdapter = NfcAdapter.getDefaultAdapter(mContext);
|
||||
LogUtils.d(TAG, "构造初始化完成,已绑定全局上下文");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例实例(线程安全,双重校验锁,适配多线程场景)
|
||||
* @param context 上下文对象
|
||||
* @return 单例工具类实例
|
||||
*/
|
||||
public static NfcRsaAuthTool getInstance(Context context) {
|
||||
if (sInstance == null) {
|
||||
synchronized (NfcRsaAuthTool.class) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new NfcRsaAuthTool(context);
|
||||
LogUtils.d(TAG, "首次创建单例实例成功");
|
||||
}
|
||||
}
|
||||
}
|
||||
LogUtils.d(TAG, "获取单例实例成功");
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
// ==================== 核心功能1:生成RSA私钥(返回Base64编码字符串,便于存储) ====================
|
||||
public String generateRsaPrivateKey() {
|
||||
LogUtils.d(TAG, "开始生成RSA私钥,密钥长度:" + RSA_KEY_SIZE);
|
||||
try {
|
||||
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(RSA_ALGORITHM);
|
||||
keyPairGen.initialize(RSA_KEY_SIZE);
|
||||
KeyPair keyPair = keyPairGen.generateKeyPair();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
String privateKeyStr = Base64.encodeToString(privateKey.getEncoded(), Base64.NO_WRAP);
|
||||
LogUtils.d(TAG, "RSA私钥生成成功,已完成Base64编码");
|
||||
return privateKeyStr;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LogUtils.e(TAG, "RSA私钥生成失败,无对应算法支持", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 核心功能2:NFC密钥读写(适配NDEF标签,兼容已格式化/未格式化场景) ====================
|
||||
/**
|
||||
* 写入Base64私钥到NFC标签,带专属标识防数据混淆
|
||||
* @param tag NFC标签对象
|
||||
* @param privateKeyStr Base64编码私钥字符串
|
||||
* @return 写入成功返回true,失败返回false
|
||||
*/
|
||||
public boolean writePrivateKeyToNfc(Tag tag, String privateKeyStr) {
|
||||
LogUtils.d(TAG, "写入NFC私钥,入参校验:Tag=" + tag + ",私钥非空=" + (privateKeyStr != null && !privateKeyStr.isEmpty()));
|
||||
if (tag == null || privateKeyStr == null || privateKeyStr.isEmpty() || mNfcAdapter == null) {
|
||||
LogUtils.w(TAG, "入参无效,写入失败(Tag/NFC适配器为空或私钥为空)");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
byte[] writeData = (NFC_KEY_TAG + privateKeyStr).getBytes(CHARSET);
|
||||
boolean result = writeNfcData(tag, writeData);
|
||||
LogUtils.d(TAG, "NFC私钥写入结果:" + result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "NFC私钥写入异常", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从NFC标签读取私钥,自动校验标识并缓存到内存
|
||||
* @param tag NFC标签对象
|
||||
* @return 有效私钥返回Base64字符串,无效返回null
|
||||
*/
|
||||
public String readPrivateKeyFromNfc(Tag tag) {
|
||||
LogUtils.d(TAG, "读取NFC私钥,Tag对象:" + tag);
|
||||
if (tag == null || mNfcAdapter == null) {
|
||||
LogUtils.w(TAG, "Tag或NFC适配器为空,读取失败");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
byte[] nfcData = readNfcData(tag);
|
||||
if (nfcData == null || nfcData.length == 0) {
|
||||
LogUtils.w(TAG, "NFC标签无有效存储数据");
|
||||
return null;
|
||||
}
|
||||
String allDataStr = new String(nfcData, CHARSET);
|
||||
if (!allDataStr.startsWith(NFC_KEY_TAG)) {
|
||||
LogUtils.w(TAG, "NFC数据无专属标识,判定为无效私钥数据");
|
||||
return null;
|
||||
}
|
||||
String privateKeyStr = allDataStr.substring(NFC_KEY_TAG.length());
|
||||
if (!privateKeyStr.isEmpty()) {
|
||||
mCachePrivateKeyStr = privateKeyStr;
|
||||
extractPublicKeyFromPrivateKeyStr(privateKeyStr);
|
||||
LogUtils.d(TAG, "NFC私钥读取成功,已缓存私钥并提取公钥");
|
||||
}
|
||||
return privateKeyStr;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "NFC私钥读取异常", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 核心功能3:本地data区密钥存储(仅应用可访问,安全存储) ====================
|
||||
/**
|
||||
* 私钥存储到应用内部data区,同步缓存到内存
|
||||
* @param privateKeyStr Base64编码私钥字符串
|
||||
* @return 存储成功返回true,失败返回false
|
||||
*/
|
||||
public boolean savePrivateKeyToLocal(String privateKeyStr) {
|
||||
LogUtils.d(TAG, "本地存储私钥,私钥非空校验:" + (privateKeyStr != null && !privateKeyStr.isEmpty()));
|
||||
if (privateKeyStr == null || privateKeyStr.isEmpty()) {
|
||||
LogUtils.w(TAG, "待存储私钥为空,存储失败");
|
||||
return false;
|
||||
}
|
||||
File keyFile = new File(mContext.getFilesDir(), LOCAL_KEY_FILE_NAME);
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(keyFile);
|
||||
fos.write(privateKeyStr.getBytes(CHARSET));
|
||||
fos.flush();
|
||||
mCachePrivateKeyStr = privateKeyStr;
|
||||
extractPublicKeyFromPrivateKeyStr(privateKeyStr);
|
||||
LogUtils.d(TAG, "私钥本地存储成功,存储路径:" + keyFile.getAbsolutePath());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "私钥本地存储失败", e);
|
||||
return false;
|
||||
} finally {
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.w(TAG, "关闭存储输出流异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从本地data区读取私钥,同步缓存到内存
|
||||
* @return 本地私钥返回Base64字符串,无文件返回null
|
||||
*/
|
||||
public String getLocalPrivateKey() {
|
||||
LogUtils.d(TAG, "开始读取本地存储私钥");
|
||||
File keyFile = new File(mContext.getFilesDir(), LOCAL_KEY_FILE_NAME);
|
||||
if (!keyFile.exists()) {
|
||||
LogUtils.w(TAG, "本地私钥文件不存在");
|
||||
return null;
|
||||
}
|
||||
FileInputStream fis = null;
|
||||
ByteArrayOutputStream bos = null;
|
||||
try {
|
||||
fis = new FileInputStream(keyFile);
|
||||
bos = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[1024];
|
||||
int len;
|
||||
while ((len = fis.read(buffer)) != -1) {
|
||||
bos.write(buffer, 0, len);
|
||||
}
|
||||
String privateKeyStr = new String(bos.toByteArray(), CHARSET);
|
||||
mCachePrivateKeyStr = privateKeyStr;
|
||||
extractPublicKeyFromPrivateKeyStr(privateKeyStr);
|
||||
LogUtils.d(TAG, "本地私钥读取成功,已同步缓存");
|
||||
return privateKeyStr;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "本地私钥读取失败", e);
|
||||
return null;
|
||||
} finally {
|
||||
try {
|
||||
if (fis != null) fis.close();
|
||||
if (bos != null) bos.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.w(TAG, "关闭读取流异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 核心功能4:私钥提取公钥(自动缓存,全局可用) ====================
|
||||
public String extractPublicKeyFromPrivateKeyStr(String privateKeyStr) {
|
||||
LogUtils.d(TAG, "从私钥提取公钥,私钥非空校验:" + (privateKeyStr != null && !privateKeyStr.isEmpty()));
|
||||
if (privateKeyStr == null || privateKeyStr.isEmpty()) {
|
||||
LogUtils.w(TAG, "待提取私钥为空,提取失败");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
byte[] priKeyBytes = Base64.decode(privateKeyStr, Base64.NO_WRAP);
|
||||
PKCS8EncodedKeySpec priSpec = new PKCS8EncodedKeySpec(priKeyBytes);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
|
||||
PrivateKey privateKey = keyFactory.generatePrivate(priSpec);
|
||||
|
||||
RSAPrivateCrtKeySpec privateCrtSpec = (RSAPrivateCrtKeySpec) keyFactory.getKeySpec(privateKey, RSAPrivateCrtKeySpec.class);
|
||||
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(privateCrtSpec.getModulus(), privateCrtSpec.getPublicExponent());
|
||||
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
|
||||
|
||||
String publicKeyStr = Base64.encodeToString(publicKey.getEncoded(), Base64.NO_WRAP);
|
||||
mCachePublicKeyStr = publicKeyStr;
|
||||
LogUtils.d(TAG, "公钥提取成功,已缓存");
|
||||
return publicKeyStr;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "公钥提取失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 核心功能5:密钥有效性校验(私钥自校验,公钥交叉校验) ====================
|
||||
/**
|
||||
* 私钥有效性校验:自加密自解密测试明文
|
||||
* @param privateKeyStr Base64编码私钥字符串
|
||||
* @return 有效返回true,无效返回false
|
||||
*/
|
||||
public boolean validatePrivateKey(String privateKeyStr) {
|
||||
LogUtils.d(TAG, "开始校验私钥有效性");
|
||||
if (privateKeyStr == null || privateKeyStr.isEmpty()) {
|
||||
LogUtils.w(TAG, "待校验私钥为空,直接判定无效");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
byte[] priBytes = Base64.decode(privateKeyStr, Base64.NO_WRAP);
|
||||
PKCS8EncodedKeySpec priSpec = new PKCS8EncodedKeySpec(priBytes);
|
||||
PrivateKey privateKey = KeyFactory.getInstance(RSA_ALGORITHM).generatePrivate(priSpec);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
|
||||
byte[] encryptData = cipher.doFinal(RSA_TEST_DATA.getBytes(CHARSET));
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
byte[] decryptData = cipher.doFinal(encryptData);
|
||||
|
||||
boolean valid = RSA_TEST_DATA.equals(new String(decryptData, CHARSET));
|
||||
LogUtils.d(TAG, "私钥有效性校验结果:" + valid);
|
||||
return valid;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "私钥校验异常,判定无效", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 公钥有效性校验:私钥加密+公钥解密测试明文
|
||||
* @param privateKeyStr 基准Base64私钥字符串
|
||||
* @param publicKeyStr 待校验Base64公钥字符串
|
||||
* @return 有效返回true,无效返回false
|
||||
*/
|
||||
public boolean validatePublicKey(String privateKeyStr, String publicKeyStr) {
|
||||
LogUtils.d(TAG, "开始校验公钥有效性,私钥非空=" + (privateKeyStr != null && !privateKeyStr.isEmpty()) + ",公钥非空=" + (publicKeyStr != null && !publicKeyStr.isEmpty()));
|
||||
if (privateKeyStr == null || publicKeyStr == null || privateKeyStr.isEmpty() || publicKeyStr.isEmpty()) {
|
||||
LogUtils.w(TAG, "私钥或公钥为空,直接判定无效");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
PrivateKey privateKey = getPrivateKeyFromStr(privateKeyStr);
|
||||
PublicKey publicKey = getPublicKeyFromStr(publicKeyStr);
|
||||
if (privateKey == null || publicKey == null) {
|
||||
LogUtils.w(TAG, "私钥或公钥转对象失败,判定无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
|
||||
byte[] encryptData = cipher.doFinal(RSA_TEST_DATA.getBytes(CHARSET));
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, publicKey);
|
||||
byte[] decryptData = cipher.doFinal(encryptData);
|
||||
|
||||
boolean valid = RSA_TEST_DATA.equals(new String(decryptData, CHARSET));
|
||||
LogUtils.d(TAG, "公钥有效性校验结果:" + valid);
|
||||
return valid;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "公钥校验异常,判定无效", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 内部辅助方法(密钥字符串转对象,仅工具类内部调用) ====================
|
||||
/**
|
||||
* 内部辅助:Base64私钥字符串转PrivateKey对象
|
||||
* @param privateKeyStr Base64编码私钥字符串
|
||||
* @return 转换成功返回对象,失败返回null
|
||||
*/
|
||||
private PrivateKey getPrivateKeyFromStr(String privateKeyStr) {
|
||||
LogUtils.d(TAG, "私钥字符串转PrivateKey对象");
|
||||
if (privateKeyStr == null || privateKeyStr.isEmpty()) {
|
||||
LogUtils.w(TAG, "私钥字符串为空,转换失败");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
byte[] priBytes = Base64.decode(privateKeyStr, Base64.NO_WRAP);
|
||||
PKCS8EncodedKeySpec priSpec = new PKCS8EncodedKeySpec(priBytes);
|
||||
return KeyFactory.getInstance(RSA_ALGORITHM).generatePrivate(priSpec);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LogUtils.e(TAG, "设备不支持RSA算法,私钥转换失败", e);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "私钥格式无效,转换失败", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部辅助:Base64公钥字符串转PublicKey对象
|
||||
* @param publicKeyStr Base64编码公钥字符串
|
||||
* @return 转换成功返回对象,失败返回null
|
||||
*/
|
||||
private PublicKey getPublicKeyFromStr(String publicKeyStr) {
|
||||
LogUtils.d(TAG, "公钥字符串转PublicKey对象");
|
||||
if (publicKeyStr == null || publicKeyStr.isEmpty()) {
|
||||
LogUtils.w(TAG, "公钥字符串为空,转换失败");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
byte[] pubBytes = Base64.decode(publicKeyStr, Base64.NO_WRAP);
|
||||
X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubBytes);
|
||||
return KeyFactory.getInstance(RSA_ALGORITHM).generatePublic(pubSpec);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LogUtils.e(TAG, "设备不支持RSA算法,公钥转换失败", e);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "公钥格式无效,转换失败", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==================== 内部NFC读写辅助方法(底层交互,对外隐藏) ====================
|
||||
/**
|
||||
* 内部辅助:读取NFC标签原始字节数据
|
||||
*/
|
||||
private byte[] readNfcData(Tag tag) {
|
||||
Ndef ndef = Ndef.get(tag);
|
||||
if (ndef != null) {
|
||||
try {
|
||||
ndef.connect();
|
||||
byte[] data = ndef.getNdefMessage().getRecords()[0].getPayload();
|
||||
ndef.close();
|
||||
return data;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "NDEF格式NFC读取异常", e);
|
||||
try { ndef.close(); } catch (IOException ex) { LogUtils.w(TAG, "关闭Ndef连接异常", ex); }
|
||||
}
|
||||
}
|
||||
LogUtils.w(TAG, "NFC标签非NDEF格式,无有效数据");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部辅助:写入字节数据到NFC标签,兼容两种标签状态
|
||||
*/
|
||||
private boolean writeNfcData(Tag tag, byte[] data) {
|
||||
Ndef ndef = Ndef.get(tag);
|
||||
if (ndef != null) return writeToNdef(ndef, data);
|
||||
|
||||
NdefFormatable formatable = NdefFormatable.get(tag);
|
||||
if (formatable != null) return writeToFormatable(formatable, data);
|
||||
|
||||
LogUtils.w(TAG, "NFC标签不支持NDEF格式,写入失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部辅助:写入数据到已格式化NDEF标签
|
||||
*/
|
||||
private boolean writeToNdef(Ndef ndef, byte[] data) {
|
||||
try {
|
||||
ndef.connect();
|
||||
ndef.writeNdefMessage(createNdefMessage(data));
|
||||
ndef.close();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "写入已格式化NFC异常", e);
|
||||
try { ndef.close(); } catch (IOException ex) { LogUtils.w(TAG, "关闭Ndef连接异常", ex); }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部辅助:格式化标签并写入数据
|
||||
*/
|
||||
private boolean writeToFormatable(NdefFormatable formatable, byte[] data) {
|
||||
try {
|
||||
formatable.connect();
|
||||
formatable.format(createNdefMessage(data));
|
||||
formatable.close();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "格式化NFC并写入异常", e);
|
||||
try { formatable.close(); } catch (IOException ex) { LogUtils.w(TAG, "关闭NdefFormatable连接异常", ex); }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部辅助:创建标准NDEF消息,适配NFC传输规范
|
||||
*/
|
||||
private android.nfc.NdefMessage createNdefMessage(byte[] payload) {
|
||||
android.nfc.NdefRecord record = new android.nfc.NdefRecord(
|
||||
android.nfc.NdefRecord.TNF_MIME_MEDIA,
|
||||
"application/octet-stream".getBytes(),
|
||||
new byte[0],
|
||||
payload
|
||||
);
|
||||
return new android.nfc.NdefMessage(new android.nfc.NdefRecord[]{record});
|
||||
}
|
||||
|
||||
// ==================== 对外公共访问方法(获取缓存/状态,简洁易用) ====================
|
||||
public String getCachePrivateKeyStr() {
|
||||
return mCachePrivateKeyStr;
|
||||
}
|
||||
|
||||
public String getCachePublicKeyStr() {
|
||||
return mCachePublicKeyStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验NFC功能是否可用(硬件支持+已开启)
|
||||
* @return 可用返回true,不可用返回false
|
||||
*/
|
||||
public boolean isNfcAvailable() {
|
||||
boolean available = mNfcAdapter != null && mNfcAdapter.isEnabled();
|
||||
LogUtils.d(TAG, "NFC当前可用性:" + available);
|
||||
return available;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空内存中密钥缓存(如退出登录场景使用)
|
||||
*/
|
||||
public void clearCache() {
|
||||
mCachePrivateKeyStr = null;
|
||||
mCachePublicKeyStr = null;
|
||||
LogUtils.d(TAG, "内存密钥缓存已清空");
|
||||
}
|
||||
}
|
||||
|
||||
59
libappbase/src/main/res/layout/activity_nfc_rsa_operate.xml
Normal file
59
libappbase/src/main/res/layout/activity_nfc_rsa_operate.xml
Normal file
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:background="@android:color/white">
|
||||
|
||||
<!-- NFC状态提示文本 -->
|
||||
<TextView
|
||||
android:id="@+id/tv_nfc_state"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="正在监听NFC卡片,请贴近设备检测密钥..."
|
||||
android:textSize="17sp"
|
||||
android:textColor="@android:color/black"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:layout_marginBottom="30dp"/>
|
||||
|
||||
<!-- 私钥显示区域 -->
|
||||
<TextView
|
||||
android:id="@+id/tv_private_key"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="私钥内容:无"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@android:color/darker_gray"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:maxLines="5"
|
||||
android:ellipsize="end"/>
|
||||
|
||||
<!-- 公钥显示区域 -->
|
||||
<TextView
|
||||
android:id="@+id/tv_public_key"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="公钥内容:无"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@android:color/darker_gray"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:maxLines="5"
|
||||
android:ellipsize="end"/>
|
||||
|
||||
<!-- 核心功能按钮(复用:保存本地/初始化密钥) -->
|
||||
<Button
|
||||
android:id="@+id/btn_create_write_key"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="功能按钮待激活"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:backgroundTint="@android:color/holo_blue_light"
|
||||
android:padding="14dp"
|
||||
android:enabled="false"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
Reference in New Issue
Block a user