自定义安全键盘

原生键盘基本介绍

原生支持输入的控件有UITextField,UITextView,UISearchBar。控件之间有一些细微的区别,这里就不再一一介绍,只要以UITextField为例介绍下原生具备输入源控件使用键盘的相关知识。

键盘通知

显示或隐藏键盘时,系统会发送以下4个通知:

  • UIKeyboardWillShowNotification// 即将显示键盘
  • UIKeyboardDidShowNotification// 已显示键盘
  • UIKeyboardWillHideNotification// 即将隐藏键盘
  • UIKeyboardDidHideNotification// 已隐藏键盘

通知中包括键盘的大小跟位置信息

1
2
3
4
5
6
7
8
9
// from 苹果提供的demo-- KeyboardAccessory
- (void)keyboardWillShow:(NSNotification *)notification {
NSDictionary *userInfo = [notification userInfo];
// 位置信息
NSValue *keyboardFrameVal = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardFrame = [keyboardFrameVal CGRectValue];
// ...
}

其中 UIKeyboardFrameBeginUserInfoKey and UIKeyboardFrameEndUserInfoKey 通知可以获取键盘的开始位置和结束位置。UIKeyboardAnimationCurveUserInfoKeyUIKeyboardAnimationDurationUserInfoKey用来获取动画类型与执行动画所用的时间。其中iOS9还添加了UIKeyboardIsLocalUserInfoKey通知,主要是考虑到iPad分屏功能(multitasking)会导致所有可见的应用在键盘弹出和隐藏时都会收到通知,通过该通知判断是否为本应用导致系统发送键盘通知。

UITextField通知

  • UITextFieldTextDidBeginEditingNotification // 开始编辑
  • UITextFieldTextDidEndEditingNotification // 结束编辑状态
  • UITextFieldTextDidChangeNotification // 文本更新

基本设置

  • secureTextEntry // 是否设置问密文显示
  • clearButtonMode // 设置清除按钮显示原则(ps:其他两个支持输入的控件没有该特性),也就是点击即可删除全部文本

UITextField代理方法

  • textField:shouldChangeCharactersInRange:replacementString // 文本更新时调用,包括增删文本
  • textFieldShouldClear // 点击清楚按钮时是否清楚文本,返回YES则清除,否则不清除文本
  • textFieldShouldReturn // 点击’Return’按钮时调用,返回NO则忽略该操作。(不过,我试了下返回YES跟NO,目前没发现区别,欢迎交流👏)
  • textFieldShouldBeginEditing // 将要开始编辑
  • textFieldDidBeginEditing // 已经开始编辑
  • textFieldShouldEndEditing// 将要结束编辑
  • textFieldDidEndEditing // 已经结束编辑

原生键盘安全隐患

主要有两个:

1.键盘输入缓存(包括敏感信息)可能被第三方应用读取—>解决方案:自定义键盘。

2.原生键盘有按键效果,屏幕录制会泄露敏感信息—>解决方案: 自定义键盘的按键不要有点击效果。

接下来主要介绍下键盘输入缓存导致的安全隐患。其中包括自动更正特性和输入预测特性,使得系统会缓存输入记录。

自动更正

念茜在这篇文章iOS安全攻防(八):键盘缓存与安全键盘—念茜中指出键盘缓存导致的安全问题。

使用简体中文输入法键盘,输入英文字符和数字字符的用户名和密码,会自动启动系统输入法自动更正提示,然后用户的输入纪录会被缓存下来。

系统键盘缓存最方便拿到的就是利用系统输入法自动更正的字符串输入记录。

缓存文件的地址是:/private/var/mobile/Library/Keyboard/dynamic-text.dat

—念茜

网上搜索到的资料是说模拟器中的键盘缓存地址:~/Library/Application Support/iPhone Simulator/User/Library/Keyboard/dynamic-text.dat,但是并没有找到该地址下的相应文件。只在该目录下 ~/Library/Keyboard/en-dynamic.lm 找到类似的文件。

同样,iOS 开发安全那些事儿也指出输入自动更正机制会缓存键盘输入数据

键盘的自动更正机制会缓存用户的输入,如果在一台越狱设备上的话很可能被第三方应用轻易地读取到缓存中的数据。

简单来说我们可以把UITextfield的autocorrectionType属性设置为No来关掉这个功能。在一些安全性要求较高的应用当中通常会自定义键盘,这样可以防止缓存被第三方应用读取到也能够防止被录屏(系统键盘按下有效果)。

输入预测(Predictive Input)

Apple-iOS8-QuickType QuickType是苹果于iOS8推出的新特性,该特性支持文本输入预测,语境预测和表情符号预测。也是从iOS8开始苹果开始支持第三方输入法,当然这是题外话。

以文本输入预测为例,在输入时键盘上方有一快捷栏显示你可能会输入的单词或句子。并且它会分析以往的输入数据(以往的对话和书写风格)显示你可能使用的字词。输入预测特性会学习你的交流方式,获取你经常使用的词汇以及可能会使用的下一个字词。相应的该特性导致系统会缓存输入数据,iOS10以前是包括敏感信息的,从而埋下安全隐患。

该功能用户可以在“设置-通用-键盘-输入预测“进行启用或者移除。支持该特性的语言列表:QuickType 键盘: 文本输入预测, 包括简体中文和繁体中文。

iOS10输入缓存安全改进

关于 iOS 10 的安全性内容 介绍了iOS10的安全改进,其中包括对键盘的输入缓存导致的安全问题。

键盘

适用于:iPhone 5 及更新机型、iPad 第 4 代及更新机型、iPod touch 第 6 代及更新机型

影响:键盘自动更正建议可能会泄漏敏感信息

说明:iOS 键盘意外缓存了敏感信息。这个问题已通过改进启发法得到解决。

CVE-2016-4746:法国的 Antoine M

自定义键盘

通过设置inputView属性。通过设置inputAccessoryView可以设置处于键盘上方的控件。

键盘基本功能

  1. 文本更新(点击按键输入文本,删除按键);
  2. 密文显示设置;
  3. 完成按钮;// 隐藏键盘,并可以在代理中设置响应响应事件
  4. 隐藏键盘按钮; // 隐藏键盘

基本功能实现

  1. 文本更新: 由于有输入源控件遵循了UIKeyInput协议,因而可以通过insertText,deleteBackward方法对文本进行更新。无输入源控件则是通过添加UITextField控件(associatedKeyboard)调用方法更新文本。
  2. 密文显示:获取文本,然后通过文本长度转换为”●”符号显示,再更新到输入源控件上。其中原生具备输入源的控件中只有UITextFiled支持该特性,因而暂且实现的框架中不对其他两个具备输入源控件实现该特性。
  3. 完成按钮:调用resignFirstResponder方法。
  4. 隐藏键盘: 调用resignFirstResponder方法

随机键盘

第一种思路: 保存键值,打乱items顺序,修改item的键值。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 思路一
NSMutableArray *orderValueItmes = [NSMutableArray arrayWithCapacity:_randomItems.count];
[_randomItems enumerateObjectsUsingBlock:^(id<JYKeyNote> keyNote, NSUInteger idx, BOOL * _Nonnull stop) {
[orderValueItmes addObject:[keyNote keyValue]];
}];
_randomItems = [_randomItems shuffled];
[_randomItems enumerateObjectsUsingBlock:^(id<JYKeyNote> keyNote, NSUInteger idx, BOOL * _Nonnull stop) {
if ([keyNote isKindOfClass:[UIButton class]]) {
[(UIButton *)keyNote setTitle:orderValueItmes[idx] forState:UIControlStateNormal];
} else if ([keyNote isKindOfClass:[UILabel class]]) {
((UILabel *)keyNote).text = orderValueItmes[idx];
} else {
}
}];

第二种思路: 保存frame值,打乱item,修改item的frame。

考虑到第一种思路即使可以对randomItem进行深拷贝,但数组中的元素还是无法深拷贝,因而会出现bug。所以最终通过保存frame值,修改Item的frame实现。具体代码可以看项目,由于frame无法存储到数组中,所以存储的可能有些ugly,欢迎拍砖。

遗留问题

  1. 不同输入源无法共用同一个自定义键盘对象。尝试共用,目前无法实现。
  2. frame存储到数组中。

无输入源控件使用键盘

官方文档:Custom Views for Data Input

使用场景: 为一些无输入源的控件添加输入响应。比如按钮,Label,UIView。

设置原生键盘

实现思路:在控件上添加UITextField控件,设为隐藏,给UIView添加手势,点击事件为UITextField 称为第一响应对象。从而变相实现给无输入源控件添加输入响应。 因而引入了associateKeyboardTextField属性。

文本更新: 通过获取associateTextfiled的文本更新输入源的文本。默认实现了UILabel,UIbutton的更新,也可以通过updateDisplayText协议自己设置更新文本方案。

设置自定义键盘

遵循上一章自定义键盘原则后,通过设置associateTextFiledinputView设置自定义键盘。

实现细节

  1. UIView扩展添加属性associateKeyboardTextField,获取的时候对象变成UITextInputTraits对象?

    一开始没找到原因所在,所以暂时通过遍历subviews变相获取UITextfield。后面发现原来是动态绑定的关联策略问题导致,修改后如下所示:

    1
    2
    3
    - (void)setAssociateKeyboardTextField:(UITextField *)associateKeyboardTextField {
    return objc_setAssociatedObject(self, &associateKeyboardTextFieldKey, associateKeyboardTextField, OBJC_ASSOCIATION_RETAIN);// 原先是OBJC_ASSOCIATION_COPY_NONATOMIC
    }
  2. 对原生键盘进行配置,遵循UITextInputTraits协议。但初始化对象时,默认值设置的样式跟原生的有区别。比如默认初始化时returnKeyTypeUIReturnKeyDefault,但弹出键盘时并不像原生的为return键,而是Go键,且选项中无法设置return键。—> 解决方案,通过获取默认的键盘的相关参数的默认值,然后根据配置中的属性值如若与默认值相等,则不尽兴赋值处理。

  3. 设置初始文本时,要考虑到调用设置自定义键盘方法时,还没设置代理,当前只有在设置代理时设置键盘的初始文本。考虑到这一因素,也在设置代理时对文本作了初始化工作。

JYKeyboard

这是封装好的自定义键盘使用库。本着原则: 简单好用,易扩展。项目地址: JYKeyboard 欢迎交流欢迎拍砖👏

使用场景

1) 设置自定义键盘,包括具备输入源的控件:如UITextfiled等,与不具备输入源的控件:如UILabel等。

2) 给不具备输入源控件设zhichi置系统键盘。

特性

  • 支持自定义键盘;
  • 支持无输入源控件设置系统键盘与自定义键盘;
  • 支持密文显示设置;

自定义键盘

  1. 引入JYKeyboard.h头文件;
  2. 自定义视图继承JYAbstractKeyboard
  3. 对键盘相应的响应事件进行绑定即可。

具体可以参考项目中基于JYAbstractKeyboard实现的AliKeyboard

设置自定义键盘

有输入源控件设置自定义键盘

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
AliKeyboard *textFieldKB = [AliKeyboard standardShuffledKeyboard];
self.inputTF.viewInputDelegate = self;
[self.inputTF setCustomKeyboard:textFieldKB];
}

无输入源控件设置自定义键盘

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
AliKeyboard *labelKB = [AliKeyboard standardShuffledKeyboard];
[self.customLabel setCustomKeyboard:labelKB];
self.customLabel.viewInputDelegate = self;
}

无输入源控件设置系统键盘

1
2
3
4
5
6
7
8
9
10
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self.label noInputControlSetSystemKeyboardWithConfigBlock:^(JYKeyboardConfig *config) {
// 配置系统键盘
config.keyboardType = UIKeyboardTypeNumberPad;
}];
self.label.viewInputDelegate = self;
}

参考资料

参考项目: NHKeyboardPro

参考项目: UUKeyboardInputView

Text Programming Guide for iOS

Custom Views for Data Input

iPhone keyboard security–stackoverflow

iOS安全攻防(八):键盘缓存与安全键盘—念茜