chore: 添加 vue-i18n 依赖并更新 package.json

This commit is contained in:
2025-08-29 12:49:55 +08:00
parent 66b6943d54
commit bf5b1b5408
3598 changed files with 929083 additions and 130 deletions

View File

@@ -2,20 +2,22 @@
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 py-6 px-4 sm:px-6 lg:px-8">
<div class="max-w-md mx-auto">
<div class="text-center mb-4">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">密码生成器</h1>
<div class="flex justify-between items-start mb-2">
<div></div>
<LanguageSwitcher />
</div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{{ $t('title') }}</h1>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
安全现代易用的密码生成工具
{{ $t('subtitle') }}
</p>
</div>
<div class="bg-white dark:bg-gray-800 shadow-lg rounded-lg p-5 space-y-5">
<!-- 说明部分 -->
<div class="bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-200 dark:border-yellow-700 rounded-lg p-3">
<h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-200">使用说明</h3>
<h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-200">{{ $t('instructions.title') }}</h3>
<ul class="mt-1 text-sm text-yellow-700 dark:text-yellow-300 list-disc pl-5 space-y-0.5">
<li>密码计算全部在本地进行确保安全</li>
<li>建议使用复杂的记忆密码</li>
<li>区分代码建议使用网站特征qq, github</li>
<li v-for="(item, index) in instructionItems" :key="index">{{ item }}</li>
</ul>
</div>
@@ -23,14 +25,14 @@
<!-- 记忆密码 -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
记忆密码
{{ $t('form.memoryPassword') }}
</label>
<div class="relative rounded-md shadow-sm">
<input
v-model="memoryPassword"
:type="showPassword ? 'text' : 'password'"
class="input pr-20"
placeholder="输入你的记忆密码"
:placeholder="$t('form.memoryPasswordPlaceholder')"
/>
<button
@click="togglePasswordVisibility"
@@ -38,7 +40,7 @@
text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200
flex items-center justify-center transition-colors duration-200"
>
{{ showPassword ? '隐藏' : '显示' }}
{{ showPassword ? $t('buttons.hide') : $t('buttons.show') }}
</button>
</div>
</div>
@@ -46,27 +48,60 @@
<!-- 区分代码 -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
区分代码
{{ $t('form.distinguishCode') }}
</label>
<div class="relative rounded-md shadow-sm">
<input
v-model="distinguishCode"
type="text"
class="input"
placeholder="输入区分代码qq"
:placeholder="$t('form.distinguishCodePlaceholder')"
/>
</div>
</div>
<!-- 密码选项 -->
<div class="space-y-3">
<!-- 算法选择 -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
密码长度
{{ $t('form.algorithm') }}
</label>
<select v-model="selectedAlgorithm" class="input py-1.5">
<option
v-for="(algorithm, key) in algorithms"
:key="key"
:value="key"
>
{{ $t('locale') === 'zh' ? algorithm.name : algorithm.nameEn }}
({{ $t('security.' + algorithm.security) }})
</option>
</select>
<div class="flex items-center justify-between mt-1">
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ $t('locale') === 'zh' ? algorithms[selectedAlgorithm].description : algorithms[selectedAlgorithm].descriptionEn }}
</p>
<button
@click="showAlgorithmInfo = !showAlgorithmInfo"
class="text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 ml-2"
>
{{ showAlgorithmInfo ? $t('algorithmInfo.hide') : $t('algorithmInfo.show') }}
</button>
</div>
</div>
<!-- 算法说明面板 -->
<div v-if="showAlgorithmInfo" class="mt-4">
<AlgorithmInfo :selected-algorithm="selectedAlgorithm" />
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{{ $t('form.passwordLength') }}
</label>
<select v-model="passwordLength" class="input py-1.5">
<option v-for="length in [10,11,12,13,14,15,16,17,18,19,20]" :key="length" :value="length">
{{ length }}
{{ length }} {{ $t('form.lengthUnit') }}
</option>
</select>
</div>
@@ -75,25 +110,25 @@
<div>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" v-model="usePunctuation" class="w-4 h-4 rounded text-primary-600 focus:ring-primary-500" />
<span class="text-sm text-gray-700 dark:text-gray-300">使用标点</span>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ $t('options.usePunctuation') }}</span>
</label>
</div>
<div>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" v-model="useUpperCase" class="w-4 h-4 rounded text-primary-600 focus:ring-primary-500" />
<span class="text-sm text-gray-700 dark:text-gray-300">大写字母</span>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ $t('options.useUpperCase') }}</span>
</label>
</div>
<div>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" v-model="useNumbers" class="w-4 h-4 rounded text-primary-600 focus:ring-primary-500" />
<span class="text-sm text-gray-700 dark:text-gray-300">使用数字</span>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ $t('options.useNumbers') }}</span>
</label>
</div>
<div>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" v-model="useSpecialChars" class="w-4 h-4 rounded text-primary-600 focus:ring-primary-500" />
<span class="text-sm text-gray-700 dark:text-gray-300">特殊字符</span>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ $t('options.useSpecialChars') }}</span>
</label>
</div>
</div>
@@ -102,7 +137,7 @@
<!-- 生成的密码 -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
生成的密码
{{ $t('form.generatedPassword') }}
</label>
<div class="flex space-x-2">
<div class="relative flex-1">
@@ -117,24 +152,14 @@
@click="copyPassword"
class="btn btn-primary py-1.5"
>
复制
{{ $t('buttons.copy') }}
</button>
</div>
</div>
<!-- 密码强度指示器 -->
<div v-if="generatedPassword" class="space-y-1">
<div class="flex justify-between text-sm">
<span class="text-gray-600 dark:text-gray-400">密码强度</span>
<span :class="passwordStrengthClass">{{ passwordStrengthText }}</span>
</div>
<div class="h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
<div
:class="passwordStrengthBarClass"
:style="{ width: passwordStrengthPercentage + '%' }"
class="h-full transition-all duration-300"
></div>
</div>
<!-- 密码强度详细分析 -->
<div v-if="generatedPassword" class="mt-4">
<PasswordStrength :strength-data="passwordStrengthData" />
</div>
<!-- 生成按钮 -->
@@ -143,7 +168,7 @@
class="w-full btn btn-primary py-2 text-base font-semibold"
:disabled="isGenerating"
>
{{ isGenerating ? '生成中...' : '生成密码' }}
{{ isGenerating ? $t('buttons.generating') : $t('buttons.generate') }}
</button>
</div>
</div>
@@ -153,10 +178,24 @@
<script>
import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import LanguageSwitcher from './components/LanguageSwitcher.vue'
import AlgorithmInfo from './components/AlgorithmInfo.vue'
import PasswordStrength from './components/PasswordStrength.vue'
import zh from './locales/zh.js'
import en from './locales/en.js'
import { generatePasswordWithAlgorithm, algorithms } from './utils/passwordAlgorithms.js'
import { analyzePasswordStrength } from './utils/passwordStrengthAnalyzer.js'
export default {
name: 'App',
components: {
LanguageSwitcher,
AlgorithmInfo,
PasswordStrength
},
setup() {
const { t } = useI18n()
const memoryPassword = ref('')
const distinguishCode = ref('')
const passwordLength = ref(16)
@@ -167,6 +206,21 @@ export default {
const showPassword = ref(false)
const generatedPassword = ref('')
const isGenerating = ref(false)
const selectedAlgorithm = ref('sha512-salt') // 默认算法
const showAlgorithmInfo = ref(false) // 控制算法说明面板显示
// 获取使用说明列表
const instructionItems = computed(() => {
const { locale } = useI18n()
const currentLocale = locale.value
// 直接从语言对象中获取数组
if (currentLocale === 'zh') {
return zh.instructions.items
} else {
return en.instructions.items
}
})
const togglePasswordVisibility = () => {
showPassword.value = !showPassword.value
@@ -181,52 +235,31 @@ export default {
const generatePassword = async () => {
if (!memoryPassword.value || !distinguishCode.value) {
alert('请填写记忆密码和区分代码')
alert(t('messages.fillRequired'))
return
}
isGenerating.value = true
try {
// 使用 Web Crypto API 进行更安全的密码生成
const encoder = new TextEncoder()
const baseData = encoder.encode(memoryPassword.value + distinguishCode.value)
// 添加随机因子,确保每次生成的密码都不同
const randomSalt = getRandomBytes(16)
const combinedData = new Uint8Array(baseData.length + randomSalt.length)
combinedData.set(baseData)
combinedData.set(randomSalt, baseData.length)
const hashBuffer = await window.crypto.subtle.digest('SHA-512', combinedData)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
// 根据选项生成密码
let password = ''
const chars = {
lowercase: 'abcdefghijklmnopqrstuvwxyz',
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
numbers: '0123456789',
special: '!@#$%^&*()_+-=[]{}|;:,.<>?'
const options = {
useUpperCase: useUpperCase.value,
useNumbers: useNumbers.value,
useSpecialChars: useSpecialChars.value
}
let availableChars = chars.lowercase
if (useUpperCase.value) availableChars += chars.uppercase
if (useNumbers.value) availableChars += chars.numbers
if (useSpecialChars.value) availableChars += chars.special
// 使用更多的熵来生成密码
const randomValues = getRandomBytes(passwordLength.value * 2)
for (let i = 0; i < passwordLength.value; i++) {
const combinedEntropy = (hashArray[i] + randomValues[i]) % availableChars.length
password += availableChars[combinedEntropy]
}
const password = await generatePasswordWithAlgorithm(
selectedAlgorithm.value,
memoryPassword.value,
distinguishCode.value,
passwordLength.value,
options
)
generatedPassword.value = password
} catch (error) {
console.error('密码生成失败:', error)
alert('密码生成失败,请重试')
alert(t('messages.generateFailed'))
} finally {
isGenerating.value = false
}
@@ -237,7 +270,7 @@ export default {
try {
await navigator.clipboard.writeText(generatedPassword.value)
alert('密码已复制到剪贴板')
alert(t('messages.passwordCopied'))
} catch (err) {
console.error('复制失败:', err)
// 如果 clipboard API 失败,使用传统方法
@@ -247,53 +280,22 @@ export default {
input.select()
document.execCommand('copy')
document.body.removeChild(input)
alert('密码已复制到剪贴板')
alert(t('messages.passwordCopied'))
}
}
const passwordStrength = computed(() => {
if (!generatedPassword.value) return 0
let strength = 0
const password = generatedPassword.value
// 长度得分
strength += Math.min(password.length * 4, 40)
// 字符类型得分
if (password.match(/[A-Z]/)) strength += 10
if (password.match(/[a-z]/)) strength += 10
if (password.match(/[0-9]/)) strength += 10
if (password.match(/[^A-Za-z0-9]/)) strength += 10
// 重复字符扣分
const uniqueChars = new Set(password).size
strength -= (password.length - uniqueChars) * 2
return Math.max(0, Math.min(100, strength))
})
const passwordStrengthPercentage = computed(() => passwordStrength.value)
const passwordStrengthText = computed(() => {
if (passwordStrength.value < 40) return '弱'
if (passwordStrength.value < 60) return '中等'
if (passwordStrength.value < 80) return '强'
return '极强'
})
const passwordStrengthClass = computed(() => {
if (passwordStrength.value < 40) return 'text-red-600 dark:text-red-400'
if (passwordStrength.value < 60) return 'text-yellow-600 dark:text-yellow-400'
if (passwordStrength.value < 80) return 'text-blue-600 dark:text-blue-400'
return 'text-green-600 dark:text-green-400'
})
const passwordStrengthBarClass = computed(() => {
if (passwordStrength.value < 40) return 'bg-red-500'
if (passwordStrength.value < 60) return 'bg-yellow-500'
if (passwordStrength.value < 80) return 'bg-blue-500'
return 'bg-green-500'
// 密码强度分析
const passwordStrengthData = computed(() => {
if (!generatedPassword.value) {
return {
score: 0,
level: 'very-weak',
percentage: 0,
details: {},
suggestions: []
}
}
return analyzePasswordStrength(generatedPassword.value)
})
return {
@@ -307,14 +309,15 @@ export default {
showPassword,
generatedPassword,
isGenerating,
selectedAlgorithm,
algorithms,
showAlgorithmInfo,
instructionItems,
togglePasswordVisibility,
generatePassword,
copyPassword,
passwordStrengthPercentage,
passwordStrengthText,
passwordStrengthClass,
passwordStrengthBarClass
passwordStrengthData
}
}
}
</script>
</script>

View File

@@ -0,0 +1,133 @@
<template>
<div class="algorithm-info">
<!-- 算法说明标题 -->
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-4">
{{ $t('algorithmInfo.title') }}
</h3>
<!-- 算法列表 -->
<div class="space-y-4">
<div
v-for="(algorithm, key) in algorithms"
:key="key"
class="algorithm-card p-4 border border-gray-200 dark:border-gray-700 rounded-lg"
:class="{ 'border-blue-500 bg-blue-50 dark:bg-blue-900/20': selectedAlgorithm === key }"
>
<!-- 算法名称和安全级别 -->
<div class="flex items-center justify-between mb-2">
<h4 class="font-medium text-gray-800 dark:text-gray-200">
{{ $t('locale') === 'zh' ? algorithm.name : algorithm.nameEn }}
</h4>
<span
class="px-2 py-1 text-xs rounded-full"
:class="getSecurityBadgeClass(algorithm.security)"
>
{{ $t('security.' + algorithm.security) }}
</span>
</div>
<!-- 算法描述 -->
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3">
{{ $t('locale') === 'zh' ? algorithm.description : algorithm.descriptionEn }}
</p>
<!-- 算法特性 -->
<div class="flex items-center space-x-4 text-xs">
<span
class="flex items-center"
:class="algorithm.deterministic ? 'text-orange-600 dark:text-orange-400' : 'text-green-600 dark:text-green-400'"
>
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path v-if="algorithm.deterministic" fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
<path v-else fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
{{ algorithm.deterministic ? $t('algorithmInfo.deterministic') : $t('algorithmInfo.random') }}
</span>
</div>
</div>
</div>
<!-- 安全建议 -->
<div class="mt-6 p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
<h4 class="font-medium text-yellow-800 dark:text-yellow-200 mb-2">
{{ $t('algorithmInfo.securityTips.title') }}
</h4>
<ul class="text-sm text-yellow-700 dark:text-yellow-300 space-y-1">
<li v-for="tip in securityTips" :key="tip">
{{ tip }}
</li>
</ul>
</div>
</div>
</template>
<script>
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { algorithms } from '../utils/passwordAlgorithms.js'
import zh from '../locales/zh.js'
import en from '../locales/en.js'
export default {
name: 'AlgorithmInfo',
props: {
selectedAlgorithm: {
type: String,
default: 'sha512-salt'
}
},
setup() {
const { locale } = useI18n()
/**
* 获取安全建议列表
* Get security tips list
* @returns {Array} 安全建议数组
*/
const securityTips = computed(() => {
const currentLocale = locale.value
if (currentLocale === 'zh') {
return zh.algorithmInfo.securityTips.items
} else {
return en.algorithmInfo.securityTips.items
}
})
/**
* 获取安全级别徽章样式
* Get security level badge class
* @param {string} security - 安全级别
* @returns {string} CSS类名
*/
const getSecurityBadgeClass = (security) => {
const classes = {
'medium': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300',
'high': 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300',
'very-high': 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300'
}
return classes[security] || classes.medium
}
return {
algorithms,
securityTips,
getSecurityBadgeClass
}
}
}
</script>
<style scoped>
.algorithm-card {
transition: all 0.2s ease-in-out;
}
.algorithm-card:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.dark .algorithm-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<div class="relative">
<button
@click="toggleDropdown"
class="flex items-center space-x-2 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300
bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md
hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500
transition-colors duration-200"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129" />
</svg>
<span>{{ currentLanguageLabel }}</span>
<svg class="w-4 h-4 transition-transform duration-200" :class="{ 'rotate-180': isOpen }"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<!-- 下拉菜单 -->
<div v-if="isOpen"
class="absolute right-0 mt-2 w-32 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600
rounded-md shadow-lg z-50 overflow-hidden">
<button
v-for="lang in languages"
:key="lang.code"
@click="switchLanguage(lang.code)"
class="w-full px-4 py-2 text-left text-sm text-gray-700 dark:text-gray-300
hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-200"
:class="{ 'bg-primary-50 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400': currentLanguage === lang.code }"
>
{{ lang.label }}
</button>
</div>
</div>
</template>
<script>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { switchLanguage as switchLang, getCurrentLanguage } from '../locales/index.js'
export default {
name: 'LanguageSwitcher',
setup() {
const { t } = useI18n()
const isOpen = ref(false)
const currentLanguage = ref(getCurrentLanguage())
/**
* 可用语言列表
* Available languages list
*/
const languages = [
{ code: 'zh', label: '中文' },
{ code: 'en', label: 'English' }
]
/**
* 当前语言标签
* Current language label
*/
const currentLanguageLabel = computed(() => {
const lang = languages.find(l => l.code === currentLanguage.value)
return lang ? lang.label : '中文'
})
/**
* 切换下拉菜单显示状态
* Toggle dropdown visibility
*/
const toggleDropdown = () => {
isOpen.value = !isOpen.value
}
/**
* 切换语言
* Switch language
*/
const switchLanguage = (locale) => {
switchLang(locale)
currentLanguage.value = locale
isOpen.value = false
}
/**
* 点击外部关闭下拉菜单
* Close dropdown when clicking outside
*/
const handleClickOutside = (event) => {
if (!event.target.closest('.relative')) {
isOpen.value = false
}
}
onMounted(() => {
document.addEventListener('click', handleClickOutside)
})
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside)
})
return {
isOpen,
currentLanguage,
currentLanguageLabel,
languages,
toggleDropdown,
switchLanguage
}
}
}
</script>

View File

@@ -0,0 +1,311 @@
<template>
<div class="password-strength-container">
<!-- 总体强度显示 -->
<div class="strength-overview mb-4">
<div class="flex items-center justify-between mb-2">
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200">
{{ $t('passwordStrength.title') }}
</h3>
<div class="flex items-center space-x-2">
<span :class="getStrengthColor(strengthData.level)" class="font-bold">
{{ $t(`passwordStrength.levels.${strengthData.level}`) }}
</span>
<span class="text-sm text-gray-600 dark:text-gray-400">
({{ strengthData.score }}/100)
</span>
</div>
</div>
<!-- 强度进度条 -->
<div class="strength-bar-container">
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-3 mb-2">
<div
:class="getStrengthBgColor(strengthData.level)"
class="h-3 rounded-full transition-all duration-500 ease-out"
:style="{ width: strengthData.percentage + '%' }"
></div>
</div>
<div class="flex justify-between text-xs text-gray-500 dark:text-gray-400">
<span>{{ $t('passwordStrength.levels.very-weak') }}</span>
<span>{{ $t('passwordStrength.levels.weak') }}</span>
<span>{{ $t('passwordStrength.levels.fair') }}</span>
<span>{{ $t('passwordStrength.levels.good') }}</span>
<span>{{ $t('passwordStrength.levels.strong') }}</span>
</div>
</div>
</div>
<!-- 详细分析 -->
<div class="strength-details" v-if="showDetails">
<h4 class="text-md font-semibold text-gray-700 dark:text-gray-300 mb-3">
{{ $t('passwordStrength.detailsTitle') }}
</h4>
<div class="space-y-3">
<!-- 长度分析 -->
<div class="detail-item">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-2">
<i :class="strengthData.details.length.passed ? 'text-green-500' : 'text-red-500'"
class="fas fa-ruler-horizontal"></i>
<span class="font-medium">{{ $t('passwordStrength.criteria.length') }}</span>
<span class="text-sm text-gray-500">({{ strengthData.details.length.value }})</span>
</div>
<div class="flex items-center space-x-2">
<span :class="strengthData.details.length.passed ? 'text-green-600' : 'text-red-600'"
class="text-sm font-medium">
{{ strengthData.details.length.score }}/100
</span>
<i :class="strengthData.details.length.passed ? 'fas fa-check-circle text-green-500' : 'fas fa-times-circle text-red-500'"></i>
</div>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1 ml-6">
{{ $t(`passwordStrength.messages.${strengthData.details.length.message}`) }}
</p>
</div>
<!-- 字符多样性分析 -->
<div class="detail-item">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-2">
<i :class="strengthData.details.variety.passed ? 'text-green-500' : 'text-red-500'"
class="fas fa-palette"></i>
<span class="font-medium">{{ $t('passwordStrength.criteria.variety') }}</span>
<span class="text-sm text-gray-500">({{ strengthData.details.variety.details.count }}/4)</span>
</div>
<div class="flex items-center space-x-2">
<span :class="strengthData.details.variety.passed ? 'text-green-600' : 'text-red-600'"
class="text-sm font-medium">
{{ strengthData.details.variety.score }}/100
</span>
<i :class="strengthData.details.variety.passed ? 'fas fa-check-circle text-green-500' : 'fas fa-times-circle text-red-500'"></i>
</div>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1 ml-6">
{{ $t(`passwordStrength.messages.${strengthData.details.variety.message}`) }}
</p>
<!-- 字符类型指示器 -->
<div class="flex space-x-2 mt-2 ml-6">
<span :class="strengthData.details.variety.details.hasLower ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-500'"
class="px-2 py-1 rounded text-xs">
{{ $t('passwordStrength.charTypes.lowercase') }}
</span>
<span :class="strengthData.details.variety.details.hasUpper ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-500'"
class="px-2 py-1 rounded text-xs">
{{ $t('passwordStrength.charTypes.uppercase') }}
</span>
<span :class="strengthData.details.variety.details.hasDigits ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-500'"
class="px-2 py-1 rounded text-xs">
{{ $t('passwordStrength.charTypes.numbers') }}
</span>
<span :class="strengthData.details.variety.details.hasSpecial ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-500'"
class="px-2 py-1 rounded text-xs">
{{ $t('passwordStrength.charTypes.special') }}
</span>
</div>
</div>
<!-- 常见密码检查 -->
<div class="detail-item">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-2">
<i :class="strengthData.details.common.passed ? 'text-green-500' : 'text-red-500'"
class="fas fa-shield-alt"></i>
<span class="font-medium">{{ $t('passwordStrength.criteria.common') }}</span>
</div>
<div class="flex items-center space-x-2">
<span :class="strengthData.details.common.passed ? 'text-green-600' : 'text-red-600'"
class="text-sm font-medium">
{{ strengthData.details.common.score }}/100
</span>
<i :class="strengthData.details.common.passed ? 'fas fa-check-circle text-green-500' : 'fas fa-times-circle text-red-500'"></i>
</div>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1 ml-6">
{{ $t(`passwordStrength.messages.${strengthData.details.common.message}`) }}
</p>
</div>
<!-- 键盘模式检查 -->
<div class="detail-item">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-2">
<i :class="strengthData.details.patterns.passed ? 'text-green-500' : 'text-red-500'"
class="fas fa-keyboard"></i>
<span class="font-medium">{{ $t('passwordStrength.criteria.patterns') }}</span>
</div>
<div class="flex items-center space-x-2">
<span :class="strengthData.details.patterns.passed ? 'text-green-600' : 'text-red-600'"
class="text-sm font-medium">
{{ strengthData.details.patterns.score }}/100
</span>
<i :class="strengthData.details.patterns.passed ? 'fas fa-check-circle text-green-500' : 'fas fa-times-circle text-red-500'"></i>
</div>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1 ml-6">
{{ $t(`passwordStrength.messages.${strengthData.details.patterns.message}`) }}
</p>
</div>
<!-- 重复字符检查 -->
<div class="detail-item">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-2">
<i :class="strengthData.details.repetition.passed ? 'text-green-500' : 'text-red-500'"
class="fas fa-repeat"></i>
<span class="font-medium">{{ $t('passwordStrength.criteria.repetition') }}</span>
<span class="text-sm text-gray-500" v-if="strengthData.details.repetition.repeatRatio">
({{ strengthData.details.repetition.repeatRatio }}%)
</span>
</div>
<div class="flex items-center space-x-2">
<span :class="strengthData.details.repetition.passed ? 'text-green-600' : 'text-red-600'"
class="text-sm font-medium">
{{ strengthData.details.repetition.score }}/100
</span>
<i :class="strengthData.details.repetition.passed ? 'fas fa-check-circle text-green-500' : 'fas fa-times-circle text-red-500'"></i>
</div>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1 ml-6">
{{ $t(`passwordStrength.messages.${strengthData.details.repetition.message}`) }}
</p>
</div>
</div>
</div>
<!-- 改进建议 -->
<div class="suggestions mt-4" v-if="strengthData.suggestions && strengthData.suggestions.length > 0">
<h4 class="text-md font-semibold text-gray-700 dark:text-gray-300 mb-3">
{{ $t('passwordStrength.suggestionsTitle') }}
</h4>
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-3">
<ul class="space-y-1">
<li v-for="suggestion in strengthData.suggestions" :key="suggestion"
class="flex items-start space-x-2 text-sm text-yellow-800 dark:text-yellow-200">
<i class="fas fa-lightbulb text-yellow-600 mt-0.5"></i>
<span>{{ $t(`passwordStrength.suggestions.${suggestion}`) }}</span>
</li>
</ul>
</div>
</div>
<!-- 展开/收起按钮 -->
<div class="mt-4 text-center">
<button
@click="showDetails = !showDetails"
class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 text-sm font-medium transition-colors"
>
<i :class="showDetails ? 'fas fa-chevron-up' : 'fas fa-chevron-down'" class="mr-1"></i>
{{ showDetails ? $t('passwordStrength.hideDetails') : $t('passwordStrength.showDetails') }}
</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { getStrengthColor, getStrengthBgColor } from '../utils/passwordStrengthAnalyzer.js';
/**
* 组件属性定义
*/
const props = defineProps({
/**
* 密码强度分析数据
* @type {Object}
*/
strengthData: {
type: Object,
required: true,
default: () => ({
score: 0,
level: 'very-weak',
percentage: 0,
details: {},
suggestions: []
})
}
});
/**
* 响应式数据
*/
const showDetails = ref(false);
/**
* 获取强度等级对应的文本颜色类
* @param {string} level - 强度等级
* @returns {string} CSS类名
*/
const getStrengthColorClass = (level) => {
return getStrengthColor(level);
};
/**
* 获取强度等级对应的背景颜色类
* @param {string} level - 强度等级
* @returns {string} CSS类名
*/
const getStrengthBgColorClass = (level) => {
return getStrengthBgColor(level);
};
</script>
<script>
export default {
name: 'PasswordStrength',
methods: {
/**
* 获取强度颜色类
* @param {string} level - 强度等级
* @returns {string} 颜色类名
*/
getStrengthColor(level) {
return getStrengthColor(level);
},
/**
* 获取强度背景颜色类
* @param {string} level - 强度等级
* @returns {string} 背景颜色类名
*/
getStrengthBgColor(level) {
return getStrengthBgColor(level);
}
}
};
</script>
<style scoped>
.password-strength-container {
@apply bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4;
}
.detail-item {
@apply bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3 border border-gray-100 dark:border-gray-600;
}
.strength-bar-container {
@apply relative;
}
/* 进度条动画 */
.strength-bar-container .h-3 {
transition: width 0.5s ease-out;
}
/* 响应式设计 */
@media (max-width: 640px) {
.password-strength-container {
@apply p-3;
}
.detail-item {
@apply p-2;
}
.flex.items-center.justify-between {
@apply flex-col items-start space-y-2;
}
}
</style>

179
src/locales/en.js Normal file
View File

@@ -0,0 +1,179 @@
/**
* English language configuration
* 英文语言配置文件
*/
export default {
// Page title and description
title: 'Password Generator',
subtitle: 'Secure, modern, and easy-to-use password generation tool',
// Instructions
instructions: {
title: 'Instructions',
items: [
'All password calculations are performed locally to ensure security',
'It is recommended to use a complex memory password',
'Distinguish codes should use website features (e.g., qq, github)'
]
},
// Form labels
form: {
memoryPassword: 'Memory Password',
memoryPasswordPlaceholder: 'Enter your memory password',
distinguishCode: 'Distinguish Code',
distinguishCodePlaceholder: 'Enter distinguish code (e.g., qq)',
passwordLength: 'Password Length',
lengthUnit: 'characters',
algorithm: 'Generation Algorithm',
generatedPassword: 'Generated Password'
},
// Password options
options: {
usePunctuation: 'Use Punctuation',
useUpperCase: 'Uppercase Letters',
useNumbers: 'Use Numbers',
useSpecialChars: 'Special Characters'
},
// Button text
buttons: {
show: 'Show',
hide: 'Hide',
copy: 'Copy',
generate: 'Generate Password',
generating: 'Generating...'
},
// Password strength
strength: {
label: 'Password Strength',
weak: 'Weak',
medium: 'Medium',
strong: 'Strong',
veryStrong: 'Very Strong'
},
// Messages
messages: {
fillRequired: 'Please fill in memory password and distinguish code',
passwordCopied: 'Password copied to clipboard',
generateFailed: 'Password generation failed, please try again',
copyFailed: 'Copy failed'
},
// Security levels
security: {
'medium': 'Medium Security',
'high': 'High Security',
'very-high': 'Very High Security'
},
// Algorithm information
algorithmInfo: {
title: 'Algorithm Information',
show: 'Show Details',
hide: 'Hide Details',
deterministic: 'Deterministic',
random: 'Random',
securityTips: {
title: 'Security Tips',
items: [
'Choose high security algorithms for better protection',
'Deterministic algorithms generate same password within time window',
'Random algorithms generate different passwords each time',
'PBKDF2 algorithm is suitable for scenarios requiring highest security',
'Regularly change memory password and distinguish code'
]
}
},
// Password Strength Analysis
passwordStrength: {
title: 'Password Strength Analysis',
detailsTitle: 'Detailed Analysis',
suggestionsTitle: 'Improvement Suggestions',
showDetails: 'Show Details',
hideDetails: 'Hide Details',
// Strength Levels
levels: {
'very-weak': 'Very Weak',
'weak': 'Weak',
'fair': 'Fair',
'good': 'Good',
'strong': 'Strong'
},
// Evaluation Criteria
criteria: {
length: 'Password Length',
variety: 'Character Variety',
common: 'Common Password Check',
patterns: 'Keyboard Pattern Check',
repetition: 'Character Repetition Check'
},
// Character Types
charTypes: {
lowercase: 'Lowercase',
uppercase: 'Uppercase',
numbers: 'Numbers',
special: 'Special Characters'
},
// Analysis Messages
messages: {
// Length related
password_empty: 'Password is empty',
too_short: 'Password too short, recommend at least 6 characters',
short: 'Password is short, recommend at least 8 characters',
good_length: 'Good password length',
very_good_length: 'Very good password length',
excellent_length: 'Excellent password length',
// Character variety related
no_characters: 'No valid characters',
single_type: 'Contains only one character type',
two_types: 'Contains two character types',
three_types: 'Contains three character types, good',
all_types: 'Contains all character types, excellent',
// Common password related
not_common: 'Not a common password',
very_common: 'This is a very common password',
contains_common: 'Contains common password fragments',
// Keyboard pattern related
no_patterns: 'No obvious keyboard patterns',
keyboard_pattern: 'Contains keyboard patterns',
sequential_pattern: 'Contains sequential character patterns',
// Character repetition related
no_repetition: 'No obvious repetition',
consecutive_repeats: 'Contains consecutive repeated characters',
high_repetition: 'Character repetition rate too high',
moderate_repetition: 'Moderate character repetition rate'
},
// Improvement Suggestions
suggestions: {
increase_length: 'Increase password length to at least 8 characters',
add_uppercase: 'Add uppercase letters',
add_lowercase: 'Add lowercase letters',
add_numbers: 'Add numbers',
add_special: 'Add special characters (!@#$%^&* etc.)',
avoid_common: 'Avoid using common passwords',
avoid_patterns: 'Avoid keyboard patterns or sequential characters',
reduce_repetition: 'Reduce character repetition'
}
},
// Language switcher
language: {
label: 'Language',
chinese: '中文',
english: 'English'
}
}

58
src/locales/index.js Normal file
View File

@@ -0,0 +1,58 @@
/**
* 国际化配置文件
* Internationalization configuration
*/
import { createI18n } from 'vue-i18n'
import zh from './zh.js'
import en from './en.js'
/**
* 获取浏览器默认语言
* Get browser default language
*/
function getDefaultLocale() {
const savedLocale = localStorage.getItem('locale')
if (savedLocale) {
return savedLocale
}
const browserLocale = navigator.language || navigator.userLanguage
if (browserLocale.startsWith('zh')) {
return 'zh'
}
return 'en'
}
/**
* 创建i18n实例
* Create i18n instance
*/
const i18n = createI18n({
legacy: false, // 使用 Composition API 模式
locale: getDefaultLocale(), // 默认语言
fallbackLocale: 'zh', // 回退语言
messages: {
zh,
en
}
})
/**
* 切换语言
* Switch language
*/
export function switchLanguage(locale) {
i18n.global.locale.value = locale
localStorage.setItem('locale', locale)
document.documentElement.lang = locale
}
/**
* 获取当前语言
* Get current language
*/
export function getCurrentLanguage() {
return i18n.global.locale.value
}
export default i18n

179
src/locales/zh.js Normal file
View File

@@ -0,0 +1,179 @@
/**
* 中文语言配置文件
* Chinese language configuration
*/
export default {
// 页面标题和描述
title: '密码生成器',
subtitle: '安全、现代、易用的密码生成工具',
// 使用说明
instructions: {
title: '使用说明',
items: [
'密码计算全部在本地进行,确保安全',
'建议使用复杂的记忆密码',
'区分代码建议使用网站特征qq, github'
]
},
// 表单标签
form: {
memoryPassword: '记忆密码',
memoryPasswordPlaceholder: '输入你的记忆密码',
distinguishCode: '区分代码',
distinguishCodePlaceholder: '输入区分代码qq',
passwordLength: '密码长度',
lengthUnit: '位',
algorithm: '生成算法',
generatedPassword: '生成的密码'
},
// 密码选项
options: {
usePunctuation: '使用标点',
useUpperCase: '大写字母',
useNumbers: '使用数字',
useSpecialChars: '特殊字符'
},
// 按钮文本
buttons: {
show: '显示',
hide: '隐藏',
copy: '复制',
generate: '生成密码',
generating: '生成中...'
},
// 密码强度
strength: {
label: '密码强度',
weak: '弱',
medium: '中等',
strong: '强',
veryStrong: '极强'
},
// 提示信息
messages: {
fillRequired: '请填写记忆密码和区分代码',
passwordCopied: '密码已复制到剪贴板',
generateFailed: '密码生成失败,请重试',
copyFailed: '复制失败'
},
// 安全级别
security: {
'medium': '中等安全',
'high': '高安全',
'very-high': '极高安全'
},
// 算法说明
algorithmInfo: {
title: '算法说明',
show: '查看详情',
hide: '收起详情',
deterministic: '确定性',
random: '随机性',
securityTips: {
title: '安全建议',
items: [
'选择高安全级别的算法以获得更好的保护',
'确定性算法在相同时间窗口内生成相同密码',
'随机性算法每次生成不同的密码',
'PBKDF2算法适合需要最高安全性的场景',
'定期更换记忆密码和区分代码'
]
}
},
// 密码强度分析
passwordStrength: {
title: '密码强度分析',
detailsTitle: '详细分析',
suggestionsTitle: '改进建议',
showDetails: '显示详情',
hideDetails: '隐藏详情',
// 强度等级
levels: {
'very-weak': '极弱',
'weak': '弱',
'fair': '一般',
'good': '良好',
'strong': '强'
},
// 评估标准
criteria: {
length: '密码长度',
variety: '字符多样性',
common: '常见密码检查',
patterns: '键盘模式检查',
repetition: '重复字符检查'
},
// 字符类型
charTypes: {
lowercase: '小写字母',
uppercase: '大写字母',
numbers: '数字',
special: '特殊字符'
},
// 分析消息
messages: {
// 长度相关
password_empty: '密码为空',
too_short: '密码过短建议至少6位',
short: '密码较短建议至少8位',
good_length: '密码长度良好',
very_good_length: '密码长度很好',
excellent_length: '密码长度优秀',
// 字符多样性相关
no_characters: '无有效字符',
single_type: '仅包含一种字符类型',
two_types: '包含两种字符类型',
three_types: '包含三种字符类型,很好',
all_types: '包含所有字符类型,优秀',
// 常见密码相关
not_common: '不是常见密码',
very_common: '这是非常常见的密码',
contains_common: '包含常见密码片段',
// 键盘模式相关
no_patterns: '无明显键盘模式',
keyboard_pattern: '包含键盘模式',
sequential_pattern: '包含连续字符模式',
// 重复字符相关
no_repetition: '无明显重复',
consecutive_repeats: '包含连续重复字符',
high_repetition: '字符重复率过高',
moderate_repetition: '字符重复率适中'
},
// 改进建议
suggestions: {
increase_length: '增加密码长度至少到8位',
add_uppercase: '添加大写字母',
add_lowercase: '添加小写字母',
add_numbers: '添加数字',
add_special: '添加特殊字符(!@#$%^&*等)',
avoid_common: '避免使用常见密码',
avoid_patterns: '避免使用键盘模式或连续字符',
reduce_repetition: '减少字符重复'
}
},
// 语言切换
language: {
label: '语言',
chinese: '中文',
english: 'English'
}
}

View File

@@ -1,5 +1,12 @@
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import i18n from './locales/index.js'
createApp(App).mount('#app')
/**
* 创建Vue应用实例并配置国际化
* Create Vue app instance and configure internationalization
*/
const app = createApp(App)
app.use(i18n)
app.mount('#app')

View File

@@ -0,0 +1,262 @@
/**
* 密码生成算法工具类
* Password generation algorithms utility
*/
/**
* 生成随机字节数组
* Generate random bytes array
* @param {number} length - 字节长度
* @returns {Uint8Array} 随机字节数组
*/
export const getRandomBytes = (length) => {
const array = new Uint8Array(length)
window.crypto.getRandomValues(array)
return array
}
/**
* SHA-512 + 随机盐算法(当前默认算法)
* SHA-512 + Random Salt Algorithm (Current default)
* @param {string} memoryPassword - 记忆密码
* @param {string} distinguishCode - 区分代码
* @param {number} length - 密码长度
* @returns {Promise<Uint8Array>} 生成的哈希数组
*/
export const sha512WithSalt = async (memoryPassword, distinguishCode, length) => {
const encoder = new TextEncoder()
const baseData = encoder.encode(memoryPassword + distinguishCode)
// 添加随机因子,确保每次生成的密码都不同
const randomSalt = getRandomBytes(16)
const combinedData = new Uint8Array(baseData.length + randomSalt.length)
combinedData.set(baseData)
combinedData.set(randomSalt, baseData.length)
const hashBuffer = await window.crypto.subtle.digest('SHA-512', combinedData)
return new Uint8Array(hashBuffer)
}
/**
* PBKDF2 算法
* PBKDF2 Algorithm
* @param {string} memoryPassword - 记忆密码
* @param {string} distinguishCode - 区分代码
* @param {number} length - 密码长度
* @param {number} iterations - 迭代次数
* @returns {Promise<Uint8Array>} 生成的密钥数组
*/
export const pbkdf2Algorithm = async (memoryPassword, distinguishCode, length, iterations = 100000) => {
const encoder = new TextEncoder()
const password = encoder.encode(memoryPassword)
const salt = encoder.encode(distinguishCode + Date.now().toString())
// 导入密码作为密钥材料
const keyMaterial = await window.crypto.subtle.importKey(
'raw',
password,
{ name: 'PBKDF2' },
false,
['deriveBits']
)
// 使用PBKDF2派生密钥
const derivedBits = await window.crypto.subtle.deriveBits(
{
name: 'PBKDF2',
salt: salt,
iterations: iterations,
hash: 'SHA-256'
},
keyMaterial,
length * 8 // 转换为位数
)
return new Uint8Array(derivedBits)
}
/**
* 双重哈希算法SHA-256 + SHA-512
* Double Hash Algorithm (SHA-256 + SHA-512)
* @param {string} memoryPassword - 记忆密码
* @param {string} distinguishCode - 区分代码
* @param {number} length - 密码长度
* @returns {Promise<Uint8Array>} 生成的哈希数组
*/
export const doubleHashAlgorithm = async (memoryPassword, distinguishCode, length) => {
const encoder = new TextEncoder()
const baseData = encoder.encode(memoryPassword + distinguishCode)
// 第一次SHA-256哈希
const firstHash = await window.crypto.subtle.digest('SHA-256', baseData)
// 添加时间戳和随机盐
const timestamp = new TextEncoder().encode(Date.now().toString())
const randomSalt = getRandomBytes(8)
const combinedData = new Uint8Array(firstHash.byteLength + timestamp.length + randomSalt.length)
combinedData.set(new Uint8Array(firstHash))
combinedData.set(timestamp, firstHash.byteLength)
combinedData.set(randomSalt, firstHash.byteLength + timestamp.length)
// 第二次SHA-512哈希
const secondHash = await window.crypto.subtle.digest('SHA-512', combinedData)
return new Uint8Array(secondHash)
}
/**
* 时间基础算法(基于当前时间的确定性算法)
* Time-based Algorithm (Deterministic algorithm based on current time)
* @param {string} memoryPassword - 记忆密码
* @param {string} distinguishCode - 区分代码
* @param {number} length - 密码长度
* @param {number} timeWindow - 时间窗口分钟默认30分钟
* @returns {Promise<Uint8Array>} 生成的哈希数组
*/
export const timeBasedAlgorithm = async (memoryPassword, distinguishCode, length, timeWindow = 30) => {
const encoder = new TextEncoder()
// 计算时间窗口每30分钟一个窗口
const currentTime = Math.floor(Date.now() / (timeWindow * 60 * 1000))
const timeData = encoder.encode(currentTime.toString())
const baseData = encoder.encode(memoryPassword + distinguishCode)
const combinedData = new Uint8Array(baseData.length + timeData.length)
combinedData.set(baseData)
combinedData.set(timeData, baseData.length)
const hashBuffer = await window.crypto.subtle.digest('SHA-512', combinedData)
return new Uint8Array(hashBuffer)
}
/**
* 自定义混合算法(结合多种哈希方法)
* Custom Hybrid Algorithm (Combining multiple hash methods)
* @param {string} memoryPassword - 记忆密码
* @param {string} distinguishCode - 区分代码
* @param {number} length - 密码长度
* @returns {Promise<Uint8Array>} 生成的哈希数组
*/
export const hybridAlgorithm = async (memoryPassword, distinguishCode, length) => {
const encoder = new TextEncoder()
const baseData = encoder.encode(memoryPassword + distinguishCode)
// 使用多种哈希算法
const sha1Hash = await window.crypto.subtle.digest('SHA-1', baseData)
const sha256Hash = await window.crypto.subtle.digest('SHA-256', baseData)
const sha512Hash = await window.crypto.subtle.digest('SHA-512', baseData)
// 组合所有哈希结果
const combinedLength = sha1Hash.byteLength + sha256Hash.byteLength + sha512Hash.byteLength
const combinedHash = new Uint8Array(combinedLength)
combinedHash.set(new Uint8Array(sha1Hash))
combinedHash.set(new Uint8Array(sha256Hash), sha1Hash.byteLength)
combinedHash.set(new Uint8Array(sha512Hash), sha1Hash.byteLength + sha256Hash.byteLength)
// 添加随机盐并进行最终哈希
const randomSalt = getRandomBytes(16)
const finalData = new Uint8Array(combinedHash.length + randomSalt.length)
finalData.set(combinedHash)
finalData.set(randomSalt, combinedHash.length)
const finalHash = await window.crypto.subtle.digest('SHA-512', finalData)
return new Uint8Array(finalHash)
}
/**
* 算法配置对象
* Algorithm configuration object
*/
export const algorithms = {
'sha512-salt': {
name: 'SHA-512 + 随机盐',
nameEn: 'SHA-512 + Random Salt',
description: '使用SHA-512哈希算法配合随机盐提供良好的安全性和随机性',
descriptionEn: 'Uses SHA-512 hash algorithm with random salt, providing good security and randomness',
security: 'high',
deterministic: false,
function: sha512WithSalt
},
'pbkdf2': {
name: 'PBKDF2',
nameEn: 'PBKDF2',
description: '基于密码的密钥派生函数使用10万次迭代抗暴力破解能力强',
descriptionEn: 'Password-Based Key Derivation Function with 100,000 iterations, strong against brute force attacks',
security: 'very-high',
deterministic: false,
function: pbkdf2Algorithm
},
'double-hash': {
name: '双重哈希',
nameEn: 'Double Hash',
description: '先使用SHA-256再使用SHA-512双重加密提高安全性',
descriptionEn: 'First SHA-256 then SHA-512, double encryption for enhanced security',
security: 'high',
deterministic: false,
function: doubleHashAlgorithm
},
'time-based': {
name: '时间基础',
nameEn: 'Time-based',
description: '基于30分钟时间窗口的确定性算法同一时间段生成相同密码',
descriptionEn: 'Deterministic algorithm based on 30-minute time windows, generates same password within time period',
security: 'medium',
deterministic: true,
function: timeBasedAlgorithm
},
'hybrid': {
name: '混合算法',
nameEn: 'Hybrid Algorithm',
description: '结合SHA-1、SHA-256、SHA-512多种哈希算法提供最高安全性',
descriptionEn: 'Combines SHA-1, SHA-256, SHA-512 hash algorithms for maximum security',
security: 'very-high',
deterministic: false,
function: hybridAlgorithm
}
}
/**
* 根据算法类型生成密码
* Generate password based on algorithm type
* @param {string} algorithmType - 算法类型
* @param {string} memoryPassword - 记忆密码
* @param {string} distinguishCode - 区分代码
* @param {number} passwordLength - 密码长度
* @param {object} options - 密码选项
* @returns {Promise<string>} 生成的密码
*/
export const generatePasswordWithAlgorithm = async (algorithmType, memoryPassword, distinguishCode, passwordLength, options) => {
const algorithm = algorithms[algorithmType]
if (!algorithm) {
throw new Error(`未知的算法类型: ${algorithmType}`)
}
// 获取哈希数组
const hashArray = await algorithm.function(memoryPassword, distinguishCode, passwordLength)
// 字符集定义
const chars = {
lowercase: 'abcdefghijklmnopqrstuvwxyz',
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
numbers: '0123456789',
special: '!@#$%^&*()_+-=[]{}|;:,.<>?'
}
// 构建可用字符集
let availableChars = chars.lowercase
if (options.useUpperCase) availableChars += chars.uppercase
if (options.useNumbers) availableChars += chars.numbers
if (options.useSpecialChars) availableChars += chars.special
// 生成密码
let password = ''
const randomValues = algorithm.deterministic ? new Uint8Array(passwordLength) : getRandomBytes(passwordLength * 2)
for (let i = 0; i < passwordLength; i++) {
const combinedEntropy = algorithm.deterministic
? hashArray[i % hashArray.length]
: (hashArray[i % hashArray.length] + randomValues[i]) % availableChars.length
password += availableChars[combinedEntropy % availableChars.length]
}
return password
}

View File

@@ -0,0 +1,344 @@
/**
* 密码强度分析工具类
* 提供全面的密码强度评估和详细分析
*/
// 常见弱密码列表
const COMMON_PASSWORDS = [
'123456', 'password', '123456789', '12345678', '12345', '1234567',
'qwerty', 'abc123', 'password123', 'admin', 'letmein', 'welcome',
'123123', 'password1', '1234', 'qwerty123', 'iloveyou', 'princess',
'monkey', 'dragon', '000000', '111111', '666666', '888888'
];
// 常见键盘模式
const KEYBOARD_PATTERNS = [
'qwerty', 'asdf', 'zxcv', '1234', 'abcd', 'qwertyuiop',
'asdfghjkl', 'zxcvbnm', '123456789', 'abcdefg'
];
// 常见重复模式
const REPEAT_PATTERNS = [
'aa', 'bb', 'cc', '11', '22', '33', 'aaa', 'bbb', '111', '222'
];
/**
* 分析密码强度
* @param {string} password - 要分析的密码
* @returns {Object} 密码强度分析结果
*/
export function analyzePasswordStrength(password) {
if (!password) {
return {
score: 0,
level: 'very-weak',
percentage: 0,
details: {
length: { score: 0, passed: false, message: 'password_empty' },
variety: { score: 0, passed: false, message: 'no_characters' },
common: { score: 0, passed: false, message: 'password_empty' },
patterns: { score: 0, passed: false, message: 'password_empty' },
repetition: { score: 0, passed: false, message: 'password_empty' }
},
suggestions: ['add_length', 'add_variety', 'avoid_common']
};
}
const analysis = {
length: analyzeLengthStrength(password),
variety: analyzeCharacterVariety(password),
common: analyzeCommonPasswords(password),
patterns: analyzeKeyboardPatterns(password),
repetition: analyzeRepetition(password)
};
// 计算总分 (满分100)
const totalScore = Math.round(
analysis.length.score * 0.25 +
analysis.variety.score * 0.25 +
analysis.common.score * 0.2 +
analysis.patterns.score * 0.15 +
analysis.repetition.score * 0.15
);
// 确定强度等级
const level = getStrengthLevel(totalScore);
// 生成改进建议
const suggestions = generateSuggestions(analysis);
return {
score: totalScore,
level,
percentage: totalScore,
details: analysis,
suggestions
};
}
/**
* 分析密码长度强度
* @param {string} password - 密码
* @returns {Object} 长度分析结果
*/
function analyzeLengthStrength(password) {
const length = password.length;
let score = 0;
let passed = false;
let message = '';
if (length < 6) {
score = Math.min(length * 10, 30);
message = 'too_short';
} else if (length < 8) {
score = 40;
message = 'short';
} else if (length < 12) {
score = 60;
passed = true;
message = 'good_length';
} else if (length < 16) {
score = 80;
passed = true;
message = 'very_good_length';
} else {
score = 100;
passed = true;
message = 'excellent_length';
}
return { score, passed, message, value: length };
}
/**
* 分析字符类型多样性
* @param {string} password - 密码
* @returns {Object} 字符多样性分析结果
*/
function analyzeCharacterVariety(password) {
const hasLower = /[a-z]/.test(password);
const hasUpper = /[A-Z]/.test(password);
const hasDigits = /[0-9]/.test(password);
const hasSpecial = /[^a-zA-Z0-9]/.test(password);
const varietyCount = [hasLower, hasUpper, hasDigits, hasSpecial].filter(Boolean).length;
let score = 0;
let passed = false;
let message = '';
switch (varietyCount) {
case 0:
score = 0;
message = 'no_characters';
break;
case 1:
score = 25;
message = 'single_type';
break;
case 2:
score = 50;
message = 'two_types';
break;
case 3:
score = 75;
passed = true;
message = 'three_types';
break;
case 4:
score = 100;
passed = true;
message = 'all_types';
break;
}
return {
score,
passed,
message,
details: { hasLower, hasUpper, hasDigits, hasSpecial, count: varietyCount }
};
}
/**
* 检查是否为常见密码
* @param {string} password - 密码
* @returns {Object} 常见密码检查结果
*/
function analyzeCommonPasswords(password) {
const lowerPassword = password.toLowerCase();
const isCommon = COMMON_PASSWORDS.includes(lowerPassword);
// 检查是否包含常见密码作为子串
const containsCommon = COMMON_PASSWORDS.some(common =>
lowerPassword.includes(common) && common.length >= 4
);
let score = 100;
let passed = true;
let message = 'not_common';
if (isCommon) {
score = 0;
passed = false;
message = 'very_common';
} else if (containsCommon) {
score = 30;
passed = false;
message = 'contains_common';
}
return { score, passed, message };
}
/**
* 分析键盘模式
* @param {string} password - 密码
* @returns {Object} 键盘模式分析结果
*/
function analyzeKeyboardPatterns(password) {
const lowerPassword = password.toLowerCase();
// 检查键盘模式
const hasKeyboardPattern = KEYBOARD_PATTERNS.some(pattern =>
lowerPassword.includes(pattern)
);
// 检查连续字符 (如 abc, 123)
const hasSequential = /(?:abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz|123|234|345|456|567|678|789)/.test(lowerPassword);
let score = 100;
let passed = true;
let message = 'no_patterns';
if (hasKeyboardPattern || hasSequential) {
score = 20;
passed = false;
message = hasKeyboardPattern ? 'keyboard_pattern' : 'sequential_pattern';
}
return { score, passed, message };
}
/**
* 分析重复字符
* @param {string} password - 密码
* @returns {Object} 重复字符分析结果
*/
function analyzeRepetition(password) {
// 检查连续重复字符 (如 aaa, 111)
const hasConsecutiveRepeats = /(.)\1{2,}/.test(password);
// 计算字符重复率
const charCount = {};
for (const char of password) {
charCount[char] = (charCount[char] || 0) + 1;
}
const maxRepeat = Math.max(...Object.values(charCount));
const repeatRatio = maxRepeat / password.length;
let score = 100;
let passed = true;
let message = 'no_repetition';
if (hasConsecutiveRepeats) {
score = 10;
passed = false;
message = 'consecutive_repeats';
} else if (repeatRatio > 0.5) {
score = 30;
passed = false;
message = 'high_repetition';
} else if (repeatRatio > 0.3) {
score = 60;
message = 'moderate_repetition';
}
return { score, passed, message, repeatRatio: Math.round(repeatRatio * 100) };
}
/**
* 根据分数确定强度等级
* @param {number} score - 总分
* @returns {string} 强度等级
*/
function getStrengthLevel(score) {
if (score < 20) return 'very-weak';
if (score < 40) return 'weak';
if (score < 60) return 'fair';
if (score < 80) return 'good';
return 'strong';
}
/**
* 生成改进建议
* @param {Object} analysis - 分析结果
* @returns {Array} 建议列表
*/
function generateSuggestions(analysis) {
const suggestions = [];
// 长度建议
if (analysis.length.score < 60) {
suggestions.push('increase_length');
}
// 字符多样性建议
if (analysis.variety.score < 75) {
const { details } = analysis.variety;
if (!details.hasUpper) suggestions.push('add_uppercase');
if (!details.hasLower) suggestions.push('add_lowercase');
if (!details.hasDigits) suggestions.push('add_numbers');
if (!details.hasSpecial) suggestions.push('add_special');
}
// 常见密码建议
if (analysis.common.score < 50) {
suggestions.push('avoid_common');
}
// 模式建议
if (analysis.patterns.score < 50) {
suggestions.push('avoid_patterns');
}
// 重复建议
if (analysis.repetition.score < 50) {
suggestions.push('reduce_repetition');
}
return suggestions;
}
/**
* 获取强度等级的颜色
* @param {string} level - 强度等级
* @returns {string} 颜色类名
*/
export function getStrengthColor(level) {
const colors = {
'very-weak': 'text-red-600',
'weak': 'text-orange-500',
'fair': 'text-yellow-500',
'good': 'text-blue-500',
'strong': 'text-green-600'
};
return colors[level] || 'text-gray-500';
}
/**
* 获取强度等级的背景颜色
* @param {string} level - 强度等级
* @returns {string} 背景颜色类名
*/
export function getStrengthBgColor(level) {
const colors = {
'very-weak': 'bg-red-500',
'weak': 'bg-orange-500',
'fair': 'bg-yellow-500',
'good': 'bg-blue-500',
'strong': 'bg-green-500'
};
return colors[level] || 'bg-gray-500';
}