添加NFC钥匙概念,使用RSA秘钥管理WinBoLL服务登录验证模块。

This commit is contained in:
2026-01-11 21:15:50 +08:00
parent 67deaa852f
commit 8f536e1d91
8 changed files with 883 additions and 7 deletions

View File

@@ -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

View File

@@ -42,4 +42,4 @@
</application>
</manifest>
</manifest>

View 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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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: 开始创建私钥并写入NFCTag=" + 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);
}
}

View File

@@ -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;
}
}
// ==================== 核心功能2NFC密钥读写适配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, "内存密钥缓存已清空");
}
}

View 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>