Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 057e4bfe3a | |||
| 2786542ffd | |||
|
|
a8329f1179 | ||
|
|
031e4823b9 | ||
|
|
17d5662453 |
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "hello",
|
||||
"name": "totp",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hello",
|
||||
"name": "totp",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.2.0"
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
{
|
||||
"accounts": [
|
||||
{
|
||||
"name": "Example Service 1",
|
||||
"username": "user1",
|
||||
"secret": "JBSWY3DPEHPK3PXP",
|
||||
"enabled": true,
|
||||
"type": "totp"
|
||||
},
|
||||
{
|
||||
"name": "GitHub",
|
||||
"username": "myusername",
|
||||
"secret": "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ",
|
||||
"enabled": true,
|
||||
"type": "totp"
|
||||
},
|
||||
{
|
||||
"name": "Steam",
|
||||
"username": "steamuser",
|
||||
"secret": "NBSWY3DPEHPK3PXPNBSWY3DPEHPK3PXP",
|
||||
"enabled": true,
|
||||
"type": "steam"
|
||||
},
|
||||
{
|
||||
"name": "Work Server",
|
||||
"username": "employee",
|
||||
"secret": "MFRGG2LTEBYGCZLSMUFA====",
|
||||
"enabled": true,
|
||||
"type": "totp"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft",
|
||||
"username": "user@example.com",
|
||||
"secret": "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ",
|
||||
"enabled": false,
|
||||
"type": "totp"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"title": "TOTP验证器",
|
||||
"refreshInterval": 20,
|
||||
"maxDisplayAccounts": 10
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
{
|
||||
"package": "com.example.totp.authenticator",
|
||||
"package": "cn.deepfal.band.TOTPauthenticator",
|
||||
"name": "TOTP",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": 1,
|
||||
"versionName": "1.1.0",
|
||||
"versionCode": 2,
|
||||
"minPlatformVersion": 1000,
|
||||
"icon": "/common/logo.png",
|
||||
"deviceTypeList": [
|
||||
@ -14,6 +14,12 @@
|
||||
},
|
||||
{
|
||||
"name": "system.vibrator"
|
||||
},
|
||||
{
|
||||
"name": "system.interconnect"
|
||||
},
|
||||
{
|
||||
"name": "system.storage"
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<list-item class="totp-item" style="height: 115px; margin-bottom: 10px; background-color: #1a1a1a; border-radius: 15px; border: 1px solid #333; flex-direction: column; justify-content: space-between; padding: 8px;" type="account" for="{{totpList}}" onclick="showDetail($idx)">
|
||||
<div style="flex-direction: column; width: 100%; margin-bottom: 8px;">
|
||||
<text style="font-size: 24px; color: #fff; font-weight: bold; margin-bottom: 3px; text-align: left; width: 100%; line-height: 26px;">{{$item.name}}</text>
|
||||
<text style="font-size: 18px; color: #999; text-align: left; width: 100%; line-height: 20px;" show="{{$item.usr}}">{{$item.usr}}</text>
|
||||
<text style="font-size: 18px; color: #999; text-align: left; width: 100%; line-height: 22px;" show="{{$item.usr}}">{{$item.usr}}</text>
|
||||
</div>
|
||||
<div style="align-items: center; justify-content: center; width: 100%; height: 45px;">
|
||||
<text style="font-size: 32px; color: #00ff88; font-family: monospace; font-weight: bold; letter-spacing: 4px; text-align: center; width: 100%; line-height: 36px;">{{$item.otp}}</text>
|
||||
@ -86,16 +86,20 @@
|
||||
|
||||
<script>
|
||||
import { TOTP, SteamTotp } from '../../utils/totp'
|
||||
import accountsConfig from '../../common/accounts.json'
|
||||
import vibrator from '@system.vibrator'
|
||||
import storage from '@system.storage'
|
||||
import interconnect from '@system.interconnect'
|
||||
|
||||
// Key class similar to BandTOTP
|
||||
class TOTPKey {
|
||||
constructor(key, name, usr = '', type = 'totp') {
|
||||
constructor(key, name, usr = '', type = 'totp', algorithm = 'SHA1', digits = 6, period = 30) {
|
||||
this.name = name
|
||||
this.usr = usr
|
||||
this.key = key
|
||||
this.type = type // 新增类型字段:'totp' 或 'steam'
|
||||
this.type = type // 'totp' 或 'steam'
|
||||
this.algorithm = algorithm
|
||||
this.digits = digits
|
||||
this.period = period
|
||||
this.update()
|
||||
}
|
||||
|
||||
@ -105,7 +109,7 @@ class TOTPKey {
|
||||
if (this.type === 'steam') {
|
||||
this.otp = SteamTotp(this.key)
|
||||
} else {
|
||||
this.otp = TOTP(this.key)
|
||||
this.otp = TOTP(this.key, this.algorithm, this.digits, this.period)
|
||||
}
|
||||
} catch (error) {
|
||||
this.otp = "ERROR"
|
||||
@ -113,6 +117,8 @@ class TOTPKey {
|
||||
}
|
||||
}
|
||||
|
||||
let interconnectConn = null
|
||||
|
||||
export default {
|
||||
private: {
|
||||
totpList: [],
|
||||
@ -130,13 +136,23 @@ export default {
|
||||
lastUpdateTime: 0,
|
||||
// 配置相关
|
||||
appTitle: "TOTP验证器",
|
||||
refreshInterval: 100
|
||||
refreshInterval: 100,
|
||||
// interconnect状态
|
||||
isConnected: false,
|
||||
connectionStatus: '未连接'
|
||||
},
|
||||
|
||||
onInit() {
|
||||
console.log('TOTP应用初始化')
|
||||
|
||||
// 加载配置文件
|
||||
this.loadAccountsFromConfig()
|
||||
// 初始化interconnect连接
|
||||
this.initInterconnect()
|
||||
|
||||
// 只从本地存储加载账户(不再从配置文件加载)
|
||||
this.loadAccountsFromStorage()
|
||||
|
||||
// 设置interconnect消息监听
|
||||
this.setupInterconnectListener()
|
||||
|
||||
// 设置初始颜色
|
||||
this.updateColors(this.timeLeft)
|
||||
@ -144,45 +160,393 @@ export default {
|
||||
this.startTimer()
|
||||
},
|
||||
|
||||
loadAccountsFromConfig() {
|
||||
initInterconnect() {
|
||||
console.log('初始化interconnect连接')
|
||||
try {
|
||||
interconnectConn = interconnect.instance()
|
||||
|
||||
// 加载设置
|
||||
if (accountsConfig.settings) {
|
||||
this.appTitle = accountsConfig.settings.title || "TOTP验证器"
|
||||
this.refreshInterval = accountsConfig.settings.refreshInterval || 100
|
||||
interconnectConn.getReadyState({
|
||||
success: data => {
|
||||
console.log('interconnect状态:', data.status)
|
||||
if (data.status === 1) {
|
||||
this.isConnected = true
|
||||
this.connectionStatus = '已连接'
|
||||
console.log('interconnect连接成功')
|
||||
} else if (data.status === 2) {
|
||||
this.isConnected = false
|
||||
this.connectionStatus = '连接失败'
|
||||
console.log('interconnect连接失败')
|
||||
}
|
||||
},
|
||||
fail: (data, code) => {
|
||||
console.log('获取interconnect状态失败, code =', code)
|
||||
this.connectionStatus = '状态检查失败'
|
||||
}
|
||||
})
|
||||
|
||||
// 在initInterconnect中不设置onmessage,使用单独的方法
|
||||
// 监听消息接收将在setupInterconnectListener中设置
|
||||
|
||||
// 监听连接打开
|
||||
interconnectConn.onopen = () => {
|
||||
console.log('interconnect连接已打开')
|
||||
this.isConnected = true
|
||||
this.connectionStatus = '已连接'
|
||||
|
||||
// 连接成功后,请求当前账户列表给手机
|
||||
this.sendCurrentAccountsToPhone()
|
||||
}
|
||||
|
||||
// 加载启用的账户
|
||||
const enabledAccounts = accountsConfig.accounts.filter(account => account.enabled)
|
||||
|
||||
// 转换为TOTPKey实例
|
||||
this.totpList = enabledAccounts.map(account => {
|
||||
return new TOTPKey(
|
||||
account.secret,
|
||||
account.name,
|
||||
account.username,
|
||||
account.type || 'totp' // 如果没有指定type,默认为totp
|
||||
)
|
||||
})
|
||||
|
||||
this.totpList.forEach(() => {
|
||||
})
|
||||
// 监听连接关闭
|
||||
interconnectConn.onclose = (data) => {
|
||||
console.log('interconnect连接已关闭, reason =', data.data, ', code =', data.code)
|
||||
this.isConnected = false
|
||||
this.connectionStatus = '连接已关闭'
|
||||
}
|
||||
|
||||
// 监听连接错误
|
||||
interconnectConn.onerror = (data) => {
|
||||
console.log('interconnect连接错误, errMsg =', data.data, ', errCode =', data.code)
|
||||
this.isConnected = false
|
||||
this.connectionStatus = '连接错误'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load accounts config:', error)
|
||||
// 如果加载配置失败,使用默认账户
|
||||
this.loadDefaultAccounts()
|
||||
console.log('初始化interconnect失败:', error)
|
||||
this.connectionStatus = '初始化失败'
|
||||
}
|
||||
},
|
||||
|
||||
loadDefaultAccounts() {
|
||||
setupInterconnectListener() {
|
||||
if (!interconnectConn) {
|
||||
console.log('interconnect连接不存在,无法设置监听')
|
||||
return
|
||||
}
|
||||
|
||||
// 完全模仿BandTOTP-Band的消息处理逻辑
|
||||
interconnectConn.onmessage = (data) => {
|
||||
try {
|
||||
console.log('接收到手机消息:', data.data)
|
||||
let parsedData = JSON.parse(data.data)
|
||||
|
||||
if (parsedData.list && Array.isArray(parsedData.list)) {
|
||||
parsedData.list.forEach((e) => {
|
||||
// 查找是否已存在相同密钥
|
||||
let index = this.totpList.findIndex((a) => e.key == a.key)
|
||||
|
||||
if (index != -1) {
|
||||
// 密钥重复:只更新名字和用户名
|
||||
this.totpList[index].name = e.name
|
||||
this.totpList[index].usr = e.usr
|
||||
} else {
|
||||
// 新密钥:创建新实例并添加
|
||||
this.totpList.push(new TOTPKey(
|
||||
e.key,
|
||||
e.name,
|
||||
e.usr || '',
|
||||
e.type || 'totp',
|
||||
e.algorithm || 'SHA1',
|
||||
e.digits || 6,
|
||||
e.period || 30
|
||||
))
|
||||
}
|
||||
})
|
||||
|
||||
// 关键:立即保存到存储(学习BandTOTP-Band)
|
||||
this.saveAccountsToStorage()
|
||||
|
||||
console.log('成功更新', this.totpList.length, '个账户')
|
||||
|
||||
// 震动反馈表示数据同步成功
|
||||
vibrator.vibrate({
|
||||
mode: 'short'
|
||||
})
|
||||
setTimeout(() => {
|
||||
vibrator.vibrate({
|
||||
mode: 'short'
|
||||
})
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 处理获取账户列表请求
|
||||
if (parsedData.cmd === 'getAccounts') {
|
||||
console.log('手机请求获取账户列表')
|
||||
this.sendCurrentAccountsToPhone()
|
||||
}
|
||||
|
||||
// 处理删除账户请求
|
||||
if (parsedData.cmd === 'deleteAccount' && parsedData.key) {
|
||||
console.log('手机请求删除账户:', parsedData.key)
|
||||
this.deleteAccountByKey(parsedData.key)
|
||||
}
|
||||
|
||||
// 处理重排序账户请求
|
||||
if (parsedData.cmd === 'reorderAccounts' && parsedData.accounts) {
|
||||
console.log('手机请求重排序账户')
|
||||
this.reorderAccounts(parsedData.accounts)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log('处理手机消息失败:', error)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
sendCurrentAccountsToPhone() {
|
||||
if (!interconnectConn || !this.isConnected) {
|
||||
console.log('interconnect未连接,无法发送账户列表')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 构造与Android app兼容的数据格式
|
||||
const accountsData = {
|
||||
list: this.totpList.map(account => ({
|
||||
name: account.name,
|
||||
usr: account.usr,
|
||||
key: account.key,
|
||||
algorithm: account.algorithm || 'SHA1',
|
||||
digits: account.digits || 6,
|
||||
period: account.period || 30,
|
||||
type: account.type || 'totp'
|
||||
}))
|
||||
}
|
||||
|
||||
console.log('发送账户列表到手机:', accountsData)
|
||||
|
||||
interconnectConn.send({
|
||||
data: accountsData,
|
||||
success: () => {
|
||||
console.log('账户列表发送成功')
|
||||
},
|
||||
fail: (data) => {
|
||||
console.log('账户列表发送失败, errMsg =', data.data, ', errCode =', data.code)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('构造发送数据失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
loadAccountsFromStorage() {
|
||||
storage.get({
|
||||
key: 'keys',
|
||||
success: (data) => {
|
||||
if (data) {
|
||||
console.log('从存储加载账户:', data)
|
||||
try {
|
||||
const parsedData = JSON.parse(data)
|
||||
if (parsedData && parsedData.list && Array.isArray(parsedData.list)) {
|
||||
// 清空现有列表,避免重复加载
|
||||
this.totpList = []
|
||||
|
||||
parsedData.list.forEach((account) => {
|
||||
this.totpList.push(new TOTPKey(
|
||||
account.key,
|
||||
account.name,
|
||||
account.usr || '',
|
||||
account.type || 'totp',
|
||||
account.algorithm || 'SHA1',
|
||||
account.digits || 6,
|
||||
account.period || 30
|
||||
))
|
||||
})
|
||||
console.log('从存储加载了', this.totpList.length, '个账户')
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('解析存储数据失败:', error)
|
||||
}
|
||||
} else {
|
||||
// 如果没有存储数据,确保列表为空
|
||||
console.log('存储中没有账户数据')
|
||||
this.totpList = []
|
||||
}
|
||||
},
|
||||
fail: (data, code) => {
|
||||
console.log('从存储加载账户失败:', code)
|
||||
// 加载失败时也确保列表为空,避免使用旧数据
|
||||
this.totpList = []
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
saveAccountsToStorage() {
|
||||
const saveData = {
|
||||
list: this.totpList.map(account => ({
|
||||
name: account.name,
|
||||
usr: account.usr,
|
||||
key: account.key,
|
||||
algorithm: account.algorithm || 'SHA1',
|
||||
digits: account.digits || 6,
|
||||
period: account.period || 30,
|
||||
type: account.type || 'totp'
|
||||
}))
|
||||
}
|
||||
|
||||
storage.set({
|
||||
key: 'keys',
|
||||
value: JSON.stringify(saveData)
|
||||
})
|
||||
console.log('账户保存到存储,数量:', this.totpList.length)
|
||||
},
|
||||
|
||||
deleteAccountByKey(keyToDelete) {
|
||||
const initialCount = this.totpList.length
|
||||
|
||||
// 根据key删除账户
|
||||
this.totpList = this.totpList.filter(account => account.key !== keyToDelete)
|
||||
|
||||
const deletedCount = initialCount - this.totpList.length
|
||||
|
||||
if (deletedCount > 0) {
|
||||
// 保存更新后的账户列表
|
||||
this.saveAccountsToStorage()
|
||||
|
||||
console.log('成功删除账户,删除数量:', deletedCount, '剩余数量:', this.totpList.length)
|
||||
|
||||
// 删除成功震动反馈
|
||||
vibrator.vibrate({
|
||||
mode: 'long'
|
||||
})
|
||||
|
||||
// 如果正在查看被删除的账户详情,关闭详情页
|
||||
if (this.showDetailPage && this.selectedItem && this.selectedItem.key === keyToDelete) {
|
||||
this.hideDetail()
|
||||
}
|
||||
|
||||
// 向手机发送删除成功确认
|
||||
this.sendDeleteConfirmationToPhone(keyToDelete, true)
|
||||
} else {
|
||||
console.log('未找到要删除的账户:', keyToDelete)
|
||||
|
||||
// 向手机发送删除失败确认
|
||||
this.sendDeleteConfirmationToPhone(keyToDelete, false)
|
||||
}
|
||||
},
|
||||
|
||||
sendDeleteConfirmationToPhone(deletedKey, success) {
|
||||
if (!interconnectConn || !this.isConnected) {
|
||||
console.log('interconnect未连接,无法发送删除确认')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const confirmationData = {
|
||||
cmd: 'deleteConfirmation',
|
||||
key: deletedKey,
|
||||
success: success
|
||||
}
|
||||
|
||||
console.log('发送删除确认到手机:', confirmationData)
|
||||
|
||||
interconnectConn.send({
|
||||
data: confirmationData,
|
||||
success: () => {
|
||||
console.log('删除确认发送成功')
|
||||
},
|
||||
fail: (data) => {
|
||||
console.log('删除确认发送失败, errMsg =', data.data, ', errCode =', data.code)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('构造删除确认数据失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
reorderAccounts(newAccounts) {
|
||||
try {
|
||||
console.log('开始重排序账户,新顺序数量:', newAccounts.length)
|
||||
|
||||
// 根据新顺序重建账户列表
|
||||
const reorderedList = []
|
||||
|
||||
newAccounts.forEach((accountData) => {
|
||||
// 在现有列表中查找匹配的账户
|
||||
const existingAccount = this.totpList.find(account => account.key === accountData.key)
|
||||
|
||||
if (existingAccount) {
|
||||
// 保持现有的TOTPKey实例,只更新顺序
|
||||
reorderedList.push(existingAccount)
|
||||
} else {
|
||||
// 如果没找到,创建新的TOTPKey实例
|
||||
console.log('创建新账户实例:', accountData.name)
|
||||
reorderedList.push(new TOTPKey(
|
||||
accountData.key,
|
||||
accountData.name,
|
||||
accountData.usr || '',
|
||||
accountData.type || 'totp',
|
||||
accountData.algorithm || 'SHA1',
|
||||
accountData.digits || 6,
|
||||
accountData.period || 30
|
||||
))
|
||||
}
|
||||
})
|
||||
|
||||
// 更新账户列表
|
||||
this.totpList = reorderedList
|
||||
|
||||
// 保存到存储
|
||||
this.saveAccountsToStorage()
|
||||
|
||||
console.log('账户重排序完成,新顺序数量:', this.totpList.length)
|
||||
|
||||
// 重排序成功震动反馈
|
||||
vibrator.vibrate({
|
||||
mode: 'short'
|
||||
})
|
||||
setTimeout(() => {
|
||||
vibrator.vibrate({
|
||||
mode: 'short'
|
||||
})
|
||||
}, 100)
|
||||
|
||||
// 向手机发送重排序成功确认
|
||||
this.sendReorderConfirmationToPhone(true)
|
||||
|
||||
} catch (error) {
|
||||
console.log('重排序账户失败:', error)
|
||||
this.sendReorderConfirmationToPhone(false)
|
||||
}
|
||||
},
|
||||
|
||||
sendReorderConfirmationToPhone(success) {
|
||||
if (!interconnectConn || !this.isConnected) {
|
||||
console.log('interconnect未连接,无法发送重排序确认')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const confirmationData = {
|
||||
cmd: 'reorderConfirmation',
|
||||
success: success
|
||||
}
|
||||
|
||||
console.log('发送重排序确认到手机:', confirmationData)
|
||||
|
||||
interconnectConn.send({
|
||||
data: confirmationData,
|
||||
success: () => {
|
||||
console.log('重排序确认发送成功')
|
||||
},
|
||||
fail: (data) => {
|
||||
console.log('重排序确认发送失败, errMsg =', data.data, ', errCode =', data.code)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('构造重排序确认数据失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
// 已移除loadAccountsFromConfig - 只能通过手机端管理账户
|
||||
|
||||
onShow() {
|
||||
this.isVisible = true
|
||||
this.updateCodes()
|
||||
this.startTimer()
|
||||
|
||||
// 页面显示时,如果已连接则发送当前账户列表
|
||||
if (this.isConnected) {
|
||||
this.sendCurrentAccountsToPhone()
|
||||
}
|
||||
},
|
||||
|
||||
onHide() {
|
||||
@ -206,6 +570,15 @@ export default {
|
||||
clearInterval(this.updateInterval)
|
||||
this.updateInterval = null
|
||||
}
|
||||
|
||||
// 清理interconnect连接
|
||||
if (interconnectConn) {
|
||||
try {
|
||||
interconnectConn = null
|
||||
} catch (error) {
|
||||
console.log('清理interconnect连接失败:', error)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateCodes() {
|
||||
@ -343,7 +716,19 @@ export default {
|
||||
vibrator.vibrate({
|
||||
mode: 'short'
|
||||
})
|
||||
},
|
||||
|
||||
onBackPress() {
|
||||
console.log('触发:onBackPress')
|
||||
// 如果详情页打开,则关闭详情页返回列表
|
||||
if (this.showDetailPage) {
|
||||
this.hideDetail()
|
||||
return true // 自己处理,不执行系统默认返回
|
||||
}
|
||||
// 否则执行系统默认返回行为(退出应用)
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -491,9 +876,6 @@ export default {
|
||||
.detail-code-digit {
|
||||
width: 30px;
|
||||
height: 50px;
|
||||
/* background-color: rgba(0, 255, 136, 0.1);
|
||||
border: 1px solid rgba(0, 255, 136, 0.3);
|
||||
border-radius: 8px; */
|
||||
margin: 0 1px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@ -508,16 +890,6 @@ export default {
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.detail-code-line {
|
||||
font-size: 36px;
|
||||
color: #00ff88;
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
text-align: center;
|
||||
line-height: 44px;
|
||||
margin: 0px 0;
|
||||
}
|
||||
|
||||
.detail-code-single {
|
||||
font-size: 32px;
|
||||
color: #00ff88;
|
||||
|
||||
@ -3,9 +3,9 @@ let timeOffset=0
|
||||
function time(){
|
||||
return Math.floor(Date.now() / 1000) + (timeOffset || 0);
|
||||
}
|
||||
function TOTP(key) {
|
||||
function TOTP(key, algorithm = 'SHA1', digits = 6, period = 30) {
|
||||
|
||||
const timeStep = 30; // 默认时间步长为 30 秒
|
||||
const timeStep = period; // 使用传入的时间周期
|
||||
const currentTime = time()
|
||||
const timeCounter = Math.floor(currentTime / timeStep).toString(16).padStart(16, '0'); // 时间计数器,16位
|
||||
|
||||
@ -13,8 +13,20 @@ function TOTP(key) {
|
||||
// 使用 Base32 解码并返回 WordArray
|
||||
const decodedKey = base32Decode(key);
|
||||
|
||||
// 使用 HMAC-SHA1 计算哈希值
|
||||
const hmacHash = CryptoJS.HmacSHA1(CryptoJS.enc.Hex.parse(timeCounter),decodedKey);
|
||||
// 根据算法选择不同的HMAC函数
|
||||
let hmacHash;
|
||||
switch (algorithm.toUpperCase()) {
|
||||
case 'SHA256':
|
||||
hmacHash = CryptoJS.HmacSHA256(CryptoJS.enc.Hex.parse(timeCounter), decodedKey);
|
||||
break;
|
||||
case 'SHA512':
|
||||
hmacHash = CryptoJS.HmacSHA512(CryptoJS.enc.Hex.parse(timeCounter), decodedKey);
|
||||
break;
|
||||
case 'SHA1':
|
||||
default:
|
||||
hmacHash = CryptoJS.HmacSHA1(CryptoJS.enc.Hex.parse(timeCounter), decodedKey);
|
||||
break;
|
||||
}
|
||||
|
||||
// 动态截取
|
||||
const hmacBytes = hexToBytes(hmacHash.toString());
|
||||
@ -25,8 +37,9 @@ function TOTP(key) {
|
||||
(hmacBytes[offset + 2] << 8) |
|
||||
hmacBytes[offset + 3];
|
||||
|
||||
// 生成 6 位 OTP
|
||||
const result = (otp % 1000000).toString().padStart(6, '0');
|
||||
// 生成指定位数的OTP
|
||||
const modulo = Math.pow(10, digits);
|
||||
const result = (otp % modulo).toString().padStart(digits, '0');
|
||||
return result
|
||||
} catch (error) {
|
||||
throw error
|
||||
@ -77,7 +90,7 @@ function bufferizeSecret(secret) {
|
||||
if (secret.match(/^[0-9a-f]{40}$/i)) {
|
||||
return CryptoJS.enc.Hex.parse(secret); // 解析为 WordArray
|
||||
} else {
|
||||
// 假设是 base32 编码
|
||||
// 假设是 base32 编码(包括Steam的标准secret)
|
||||
return base32Decode(secret); // 解析为 WordArray
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user