
译者 | 陈峻
审校 | 重楼

你是否想象过,登录自己的电脑系统无需密码,仅是通过分析你的打字方式来完成身份验证,而且无需额外的硬件,无需面部扫描,也无需指纹识别,你只需手指在键盘上按照自然节奏击键。这是一种非常优雅的网络安全持续身份验证方案--击键动态(keystroke dynamics)。其背后的原理就在于每次打字时,你都会创建一个独特的数字签名。毕竟你按住“a”键的方式,在“th”之间的毫秒级停顿,以及在按下空格键之前的略微犹豫,这些微模式都和你的书写笔迹一样独特,比简单的密码更难被复制或盗用。
持续验证即服务
传统的身份验证往往是开关式的:要么准入,要么拒绝。即:在一次性输入信任凭据后,系统就会信任整个会话。但是,如果有人肩窥(shoulder-surf)了你的密码怎么办?如果你离开一台没有上锁的终端怎么办?如果攻击者远程劫持你的身份验证会话怎么办?而持续身份验证通过不断地询问:“这还是同一个人吗?”来解决此类问题。它已不是门口的安全检查站,而是在你工作时隐形持续验证的后台服务。
了解击键动态
击键动态可以捕捉个体独特性的模式。它不仅记录了个体按下的按键,还记录了按键的方式。你可以把它想象成生物行为识别技术,即通过个体的行为来识别验证,而不仅仅是他们所知道的或拥有的传统验证维度。
下面,让我们来深入了解击键事件的序列。比如:
复制
========================================
原始击键: "hello"
Key: h e l l o
| | | | |
▼ ▼ ▼ ▼ ▼
Press ●----●----●----●----●
| | | | |
Release ●----●----●----●----●
| | | | |
Time: 0 50 120 180 220 280 (ms)
Dwell Times (Press to Release):
h: 45ms e: 38ms l: 42ms l: 35ms o: 48ms
Flight Times (Release to Next Press):
h→e: 25ms e→l: 28ms l→l: 18ms l→o: 12ms1.2.3.4.5.6.7.8.9.10.11.12.13.14.
此处的击键测量指标包括:
停留时间(Dwell Time):按住一个按键多长时间。有些人是按键点击者(即:快速按下就释放),而另一些人则是按键持续者(即:更长的按压持续时间)。击打时间(Flight Time):从松开一个键到按下一个键之间的间隔。这揭示了自然的打字节奏和手指协调模式。按压动态(Pressure Dynamics):即在压敏键盘上,你按键的力量。通常,紧张的打字者可能会更用力地按压,放松的打字者则可能会更轻松。打字速度(Typing Velocity):不仅仅包括每分钟的单词量,还有着输入单词和句子中的加速和减速模式。下面展示的是三个个体打字模式的比较:
复制
• ====================================
User A (Fast Typer):
Dwell: ■■(30ms avg) Flight: ■ (15ms avg)
Pattern: ●-●-●-●-●-●-●-● (Quick, consistent rhythm)
User B (Deliberate Typer):
Dwell: ■■■■ (65ms avg) Flight: ■■■ (45ms avg)
Pattern: ●---●---●---●--- (Slower, thoughtful pace)
User C (Variable Typer):
Dwell: ■■■ (50ms avg) Flight: ■■ (varies 10-80ms)
Pattern: ●--●-●----●--●- (Irregular, context-dependent)1.2.3.4.5.6.7.8.9.10.
深度学习架构
由原始按键产生的数据往往是凌乱、高维度且充满“噪音”的。而传统的机器学习方法通常需要掺杂时间复杂度和个人差异性。下面我们来看看深度学习的数据流架构:
复制
===============================
Raw Keystrokes → Feature Extraction → Model Training → Authentication
↓ ↓ ↓ ↓
[h][e][l][l] [Dwell Times] [CNN/RNN] [User/Imposter]
Time stamps → [Flight Times] → Training → Decision
Press/Release [Velocities] [Patterns] [Confidence]
Full Pipeline Visualization:
===========================
Input Layer: [●●●●●●●●●●] (Keystroke sequence)
↓
Feature Layer: [■■■■] (Temporal features)
↓ ↘
CNN Branch: [▲▲▲] →[▼] (Pattern detection)
↓ ↘
RNN Branch: [◆◆◆] → [♦] (Sequence modeling)
↓
Fusion Layer: [⬢] (Combined features)
↓
Output: [0.87] (Authentication score)1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.
用于模式识别的卷积神经网络(CNN)
CNN在寻找空间模式方面非常出色,我们可以通过将击键序列视为一维信号或将其转换为二维表示,来进行击键分析。下面是CNN架构的击键分析:
复制
=======================================
Input: Keystroke Sequence (100 timesteps × 4 features)
┌─────────────────────────────────────────────────┐
│ Dwell │■■□■■■□□■■■□■■□□■■■□■■■□□■■■□■■□□■■■□. │
│ Flight│□■■□□■■□■■□□■■□■■□□■■□■■□□■■□■■□□■■□. │
│ Press │■□■■□■□■■□■■□■□■■□■■□■□■■□■■□■□■■□■■. │
│ Velocity│□□■■■□□■■■□□■■■□□■■■□□■■■□□■■■□□■■■. │
└─────────────────────────────────────────────────┘
↓ Conv2D (32 filters, 3×1)
┌─────────────────────────────────────────────────┐
│ Feature Maps │
│▲▲▲▲ ▼▼▼▼ ◆◆◆◆ ●●●● ■■■■ □□□□ ★★★★ ☆☆☆☆ │
│ Filter responses detecting local patterns │
└─────────────────────────────────────────────────┘
↓ MaxPool + Conv2D (64 filters)
┌─────────────────────────────────────────────────┐
│ Higher-level Features │
│ ████ ▓▓▓▓ ░░░░ ▒▒▒▒ ■■■■ □□□□ │
│ Complex typing pattern detectors │
└─────────────────────────────────────────────────┘
↓ Global Average Pooling
[Feature Vector]
↓ Dense Layer
[Authentication Score: 0.92]
Pattern Recognition Examples:
============================
Filter 1: ■□■□■□ (Detects alternating dwell patterns)
Filter 2: ■■■□□□ (Detects burst typing followed by pause)
Filter 3: □■■■■□ (Detects acceleration patterns)
Filter 4: ■□□■□□ (Detects hesitation patterns)1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.
用于按键特征提取的CNN架构示例:
复制
import tensorflow as tf
def build_keystroke_cnn(sequence_length, num_features):
model = tf.keras.Sequential([
# Reshape input for 1D convolution
tf.keras.layers.Reshape((sequence_length, num_features, 1)),
# First convolutional block - captures local typing patterns
tf.keras.layers.Conv2D(32, (3, 1), activatinotallow=relu, padding=same),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.MaxPooling2D((2, 1)),
# Second block - captures mid-level temporal patterns
tf.keras.layers.Conv2D(64, (3, 1), activatinotallow=relu, padding=same),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.MaxPooling2D((2, 1)),
# Third block - high-level feature extraction
tf.keras.layers.Conv2D(128, (3, 1), activatinotallow=relu, padding=same),
tf.keras.layers.GlobalAveragePooling2D(),
# Dense layers for classification
tf.keras.layers.Dense(256, activatinotallow=relu),
tf.keras.layers.Dropout(0.3),
tf.keras.layers.Dense(1, activatinotallow=sigmoid) # Binary: authentic user or not
])
return model1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.
可见,CNN能够学会识别你打字“信号”中的特征模式。就像它识别图像中的边缘一样,它可以识别击键序列中独特的时序符号。
用于时间建模的循环神经网络(RNN)
虽然CNN非常适合模式识别,但RNN才是专门为顺序数据设计的。毕竟,击键打字的本质是时序的,它与时间联系紧密。下面是基于RNN/LSTM架构的击键分析:
复制
==============================================
Keystroke Sequence: [k1] → [k2] → [k3] → [k4] → ... → [kn]
↓ ↓ ↓ ↓ ↓
LSTM Layer 1: [h1] → [h2] → [h3] → [h4] → ... → [hn]
↓ ↓ ↓ ↓ ↓
Memory States: [c1] [c2] [c3] [c4] ... [cn]
LSTM Layer 2: [h1] → [h2] → [h3] → [h4] → ... → [hn]
↓ ↓ ↓ ↓ ↓
Final Output: [Output]
↓
[Auth Score: 0.85]
LSTM Cell Internal Process:
==========================
Previous: h(t-1), c(t-1) Current Input: x(t)
↓ ↓
┌─────────────────────────────────────┐
│ Forget Gate: f(t) = σ(Wf·[h,x]+bf) │← Decides what to forget
│ Input Gate: i(t) = σ(Wi·[h,x]+bi) │← Decides what to update
│ Candidate: C̃(t) = tanh(Wc·[h,x]) │← New candidate values
│ Output Gate: o(t) = σ(Wo·[h,x]+bo) │← Controls output
└─────────────────────────────────────┘
↓
Memory Update: c(t) = f(t)*c(t-1) + i(t)*C̃(t)
Hidden State: h(t) = o(t) * tanh(c(t))
Temporal Pattern Learning:
=========================
Time: t1 t2 t3 t4 t5 t6
Input: [●] [●] [●] [●] [●] [●]
Pattern: Fast→Fast→Slow→Fast→Fast→Slow
Memory: ■ ■■ ■■■ ■■ ■■■ ■■■■
↑ ↑ ↑ ↑ ↑ ↑
Learn Build Slow Reset Repeat Confirm
rhythm context pattern state rhythm pattern1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.
而将上述RNN/LSTM架构转换为Python则为:
复制
def build_keystroke_rnn(sequence_length, num_features):
model = tf.keras.Sequential([
# LSTM layers to capture typing rhythm and dependencies
tf.keras.layers.LSTM(128, return_sequences=True, dropout=0.2),
tf.keras.layers.LSTM(64, return_sequences=True, dropout=0.2),
tf.keras.layers.LSTM(32, dropout=0.2),
# Dense layers for user identification
tf.keras.layers.Dense(128, activatinotallow=relu),
tf.keras.layers.Dropout(0.3),
tf.keras.layers.Dense(64, activatinotallow=relu),
tf.keras.layers.Dense(1, activatinotallow=sigmoid)
])
return model1.2.3.4.5.6.7.8.9.10.11.12.13.
可见,RNN保留了你击键历史记录的内部记忆,能够通过“快速-快速-暂停-快速”、或是你在输入数字之前始终会放慢速度的习惯,了解到某些字母组合。
混合CNN-RNN架构
更强大的方法是将两者相结合,即:使用CNN提取本地击键模式,而RNN模拟时间依赖关系。例如:
复制
def build_hybrid_keystroke_model(sequence_length, num_features):
# Input layer
inputs = tf.keras.layers.Input(shape=(sequence_length, num_features))
# CNN branch for pattern extraction
cnn_branch = tf.keras.layers.Reshape((sequence_length, num_features, 1))(inputs)
cnn_branch = tf.keras.layers.Conv2D(64, (3, 1), activatinotallow=relu, padding=same)(cnn_branch)
cnn_branch = tf.keras.layers.MaxPooling2D((2, 1))(cnn_branch)
cnn_branch = tf.keras.layers.Conv2D(32, (3, 1), activatinotallow=relu, padding=same)(cnn_branch)
cnn_branch = tf.keras.layers.Reshape((-1, 32))(cnn_branch)
# RNN branch for temporal modeling
rnn_branch = tf.keras.layers.LSTM(64, return_sequences=True)(inputs)
rnn_branch = tf.keras.layers.LSTM(32)(rnn_branch)
# Combine features
combined = tf.keras.layers.Concatenate()([
tf.keras.layers.GlobalAveragePooling1D()(cnn_branch),
rnn_branch
])
# Final classification
outputs = tf.keras.layers.Dense(128, activatinotallow=relu)(combined)
outputs = tf.keras.layers.Dropout(0.3)(outputs)
outputs = tf.keras.layers.Dense(1, activatinotallow=sigmoid)(outputs)
model = tf.keras.Model(inputs=inputs, outputs=outputs)
return model1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.
实际训练
我们接着来看看对于数据的预处理训练。请参考如下代码:
复制
def preprocess_keystroke_data(raw_data):
"""
Convert raw keystroke events into feature vectors
"""
features = []
for session in raw_data:
# Calculate dwell times
dwell_times = [event.release_time - event.press_time for event in session]
# Calculate flight times
flight_times = []
for i in range(len(session) - 1):
flight_time = session[i+1].press_time - session[i].release_time
flight_times.append(flight_time)
# Normalize to handle different typing speeds
dwell_times = normalize_sequence(dwell_times)
flight_times = normalize_sequence(flight_times)
# Create fixed-length sequences
feature_vector = create_fixed_sequence(dwell_times, flight_times)
features.append(feature_vector)
return np.array(features)1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.
训练策略示例
训练的关键是尽快发现那些异常检测到的问题,而不是进行传统的分类。你无需尝试识别每一个可能的用户,而是要识别出当前用户在什么时候属于未经过身份验证的用户。请参考如下代码:
复制
# Training approach
def train_keystroke_authenticator(user_data, imposter_data):
# Combine CNN and RNN model
model = build_hybrid_keystroke_model(sequence_length=100, num_features=4)
# Use focal loss to handle class imbalance
model.compile(
optimizer=adam,
loss=binary_focal_crossentropy, # Better for imbalanced data
metrics=[accuracy, precision, recall]
)
# Training with data augmentation
X_train, X_val, y_train, y_val = train_test_split(
features, labels, test_size=0.2, stratify=labels
)
# Use callbacks for adaptive training
callbacks = [
tf.keras.callbacks.EarlyStopping(patience=10),
tf.keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=5),
tf.keras.callbacks.ModelCheckpoint(best_model.h5, save_best_notallow=True)
]
model.fit(X_train, y_train,
validation_data=(X_val, y_val),
epochs=100,
batch_size=32,
callbacks=callbacks)
return model1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.
部署和监控
在真实场景中,系统会通过实时推理,进而做出持续的身份验证决策。请参考如下代码:
复制
class KeystrokeAuthenticator:
def __init__(self, model_path, window_size=50):
self.model = tf.keras.models.load_model(model_path)
self.window_size = window_size
self.keystroke_buffer = []
self.confidence_threshold = 0.7
def process_keystroke(self, keystroke_event):
# Add to rolling buffer
self.keystroke_buffer.append(keystroke_event)
# Keep only recent keystrokes
if len(self.keystroke_buffer) > self.window_size:
self.keystroke_buffer.pop(0)
# Make prediction if we have enough data
if len(self.keystroke_buffer) >= self.window_size:
features = self.extract_features(self.keystroke_buffer)
confidence = self.model.predict(features.reshape(1, -1))[0][0]
if confidence < self.confidence_threshold:
return "AUTHENTICATION_FAILED"
else:
return "AUTHENTICATED"
return "INSUFFICIENT_DATA"1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.
自适应阈值
不过,就动态平衡生物的行为识别而言,静态阈值技术往往无法应对个体的变化、系统的差异、以及上下文等问题。为此,我们可以实施基于以下内容的动态阈值。
最近的认证成功率一整天或一周的模式特征键盘与设备的上下文用户的反馈(如果有)
运行测试
下面,我们以合法用户的正常输入,以及为冒名顶替者的输入来运行上述脚本。其中,CNN层在训练期间会提取空间特征,而RNN在测试期间会处理时间序列,模拟持续身份验证。同时,该模型的输出具有置信分数的预测(即,判定合法用户或冒名顶替者)。
注意:这是一个简化的原型。在真实场景中,你可能需要更大的数据集、强大的预处理、以及用户同意等道德考虑。
现实世界的实施挑战
数据收集和隐私
击键动态需要持续监控用户的输入。不过,这会引起严重的隐私问题,毕竟它会记录个体输入的所有内容。为此,可参考的解决方案包括:
设备处理:让原始按键的输入数据永远不会离开用户的设备仅提取特征:存储时序模式,而非具体按键输入内容差异隐私:添加受控的“噪音”,以保护个体的打字习惯用户同意和透明度:就正在监控的内容与用户进行清晰的沟通处理变异性
当个体疲惫、压力大、使用不同的键盘,甚至坐在不同的位置时,其击键打字方式会有所不同。对此,此类系统必须考虑到:
上下文适应:充分考虑不同场景的不同模型,包括:笔记本电脑或台式机键盘、早上或晚上的输入持续学习:构建能适应击键模式逐渐变化的模型信心评分:有时,系统说出“我不确定”,要比做出错误的认证结论要更好性能要求
要能够实现持续身份验证就必须:
够快:具有亚秒级的决策能力资源高效:不可过于消耗电池电量或减慢系统速度准确性:降低假阳性率(不要锁定合法用户)和假阴性率(不要放过攻击者)
结论
击键动态的持续身份验证应用,为现代网络安全提供了一种强大、用户友好的方法。为了利用击键输入行为的独特模式,我们通过混合CNN+RNN深度学习架构,进而实现了对持续测试时间的动态建模,提供了隐形的实时身份验证。随着网络威胁的演变,掌握此类技术将使你能够处于数字安全的最前沿。
译者介绍
陈峻(Julian Chen),51CTO社区编辑,具有十多年的IT项目实施经验,善于对内外部资源与风险实施管控,专注传播网络与信息安全知识与经验。
原文标题:What If Your Unique Typing Style Could Become Your Seamless Password?,作者:Alok Upadhyay