如何在 Google 验证器、iOS 中的 Authy 等基于时间的 OTP 之间保持同步
How to maintain sync between time based OTPs of apps like Google authenticator, Authy in iOS
我正在开发一个使用计时器每 30 秒生成一次顶部的应用程序。我从扫描的二维码中获取密钥,并将它们附加到一个模型数组中,对于每个密钥,我生成 tOTP 并将它们附加到另一个模型数组中。使用该模型数组,我正在填充表视图。
tableviewcell 包含用于显示 otp 的标签和用于跟踪进度的自定义循环进度视图。主要问题是我无法与 Google Authenticator 等其他 TOTP 应用保持同步。当我每秒 运行 计时器时,我可以每秒从 TOTP 生成器库生成 otps,并且可以通过重新加载 tableview 来更新标签。但是此功能会影响进度视图,删除编辑表视图单元格,因为我每秒 运行ning 计时器生成 otp 并重新加载表视图。
希望有人帮忙...
这是我的代码...
class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource,AddTOTPDelegate, UIGestureRecognizerDelegate {
var tOTPS = [TOTP]()
var tOTPModel = [TOTP]()
var secretKeys = [String]()
var generator: OTPGenerator?
var timer: Timer?
var currentTimeInterval = TimeInterval()
@IBOutlet weak var tOtpTableView: UITableView!
@IBOutlet weak var btnInfo: UIBarButtonItem!
@IBOutlet weak var btnAddQRCode: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.tOtpTableView.tableFooterView = UIView()
self.emptyDataString = "Click + to add new account"
let longPressGesture:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longPress(_:)))
longPressGesture.minimumPressDuration = 1.0
longPressGesture.delegate = self
self.tOtpTableView.addGestureRecognizer(longPressGesture)
setUpViews()
getTotps()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
if self.tOTPS.isEmpty == false {
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(generateTOTP), userInfo: nil, repeats: true)
self.timer?.fire()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getTotps() {
if let decodedData = KeychainWrapper.standard.data(forKey: "tOtps") {
self.tOTPS = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as! [TOTP]
}
}
@IBAction func handleAddQRCode(_ sender: UIButton) {
let controllerToPresent = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "QRScannerController") as! QRScannerController
controllerToPresent.delegate = self
controllerToPresent.tOTPS = self.tOTPS
self.timer?.invalidate()
DispatchQueue.main.async {
self.navigationController?.pushViewController(controllerToPresent, animated: true)
}
}
@objc func generateTOTP() {
self.tOTPModel = []
for tOtpObject in self.tOTPS {
self.generator = Generator.generatorWithSecretKey(key: tOtpObject.secretKey)
let tOtp = (self.generator as! TOTPGenerator).generateOTP()
self.tOTPModel.append(TOTP(secretKey: tOtp!, issuer: tOtpObject.issuer, scheme: tOtpObject.scheme, createdDate: tOtpObject.createdDate))
}
self.tOtpTableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.tOTPModel.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "totpCell", for: indexPath) as! TotpViewCell
let tOTP = self.tOTPModel[indexPath.section]
cell.lblTOTP.text = tOTP.secretKey.separate(every: 3, with: " ")
cell.lblIssuer.text = tOTP.issuer
cell.lblCreatedDate.text = "Created Date: \(tOTP.createdDate)"
cell.lblCreatedDate.isHidden = true
cell.issuerConstraint.isActive = true
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
currentTimeInterval = (self.timer?.fireDate.timeIntervalSince(Date()))!
let fromValue = 1
let toValue = 0
cell.progressView.handleAnimation(fromValue: fromValue, tVal: toValue, duration: currentTimeInterval)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: .normal, title: "Edit") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Enter the issuer", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in
let textField = alertController.textFields![0] as UITextField
self.tOTPModel[indexPath.section].issuer = textField.text!
self.tOTPS[indexPath.section].issuer = textField.text!
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.reloadData()
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alertController.addTextField(configurationHandler: {(textField : UITextField!) -> Void in
let tOTP = self.tOTPModel[indexPath.section]
textField.placeholder = "Enter Issuer"
textField.text = tOTP.issuer
})
self.present(alertController, animated: true, completion: nil)
}
editAction.backgroundColor = UIColor(red: 0/255, green: 145/255, blue: 147/255, alpha: 1.0)
let deleteAction = UITableViewRowAction(style: .normal, title: "Delete") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Are you sure you want remove this account?", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: { (action) in
self.tOTPS.remove(at: indexPath.section)
self.tOTPModel.remove(at: indexPath.section)
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.deleteSections([indexPath.section], with: .automatic)
tableView.reloadData()
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: { (action) in
self.dismiss(animated: true, completion: nil)
})
alertController.addAction(cancelAction)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
deleteAction.backgroundColor = .red
return [editAction,deleteAction]
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
// Delegate Method that adds tOtp from QRCode scanner controller
func addTOTP(withSecret secret: String, issuer: String, scheme: String,createdDate: String) {
self.tOTPS.append(TOTP(secretKey: secret, issuer: issuer, scheme: scheme, createdDate: createdDate))
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
}
}
这是模型对象...
class TOTP: NSObject, NSCoding {
var secretKey: String
var issuer: String
var scheme: String
var createdDate: String
init(secretKey: String, issuer: String, scheme: String, createdDate: String) {
self.secretKey = secretKey
self.issuer = issuer
self.scheme = scheme
self.createdDate = createdDate
}
func encode(with aCoder: NSCoder) {
aCoder.encode(secretKey, forKey: "secretKey")
aCoder.encode(issuer, forKey: "issuer")
aCoder.encode(scheme, forKey: "scheme")
aCoder.encode(createdDate, forKey: "timeInterval")
}
required init?(coder aDecoder: NSCoder) {
secretKey = aDecoder.decodeObject(forKey: "secretKey") as! String
issuer = aDecoder.decodeObject(forKey: "issuer") as! String
scheme = aDecoder.decodeObject(forKey: "scheme") as! String
createdDate = aDecoder.decodeObject(forKey: "timeInterval") as! String
}
}
这是生成 TOTP 的生成器 class...
class Generator {
static func generatorWithSecretKey(key: String) -> OTPGenerator {
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
let secretKey = MF_Base32Codec.data(fromBase32String: key)
return TOTPGenerator(secret: secretKey, algorithm: OTPGenerator.defaultAlgorithm(), digits: 6, period: 30)
}
}
我不明白你为什么需要单独的 tOTPModel
数组。我会删除它并只使用 tOTPS
数组,我会将生成器放在它所属的 TOTP
对象中。
现在您可以在每次计时器计时时重新加载可见行。
class TOTP: NSObject, NSCoding {
var secretKey: String
var issuer: String
var scheme: String
var createdDate: String
private var generator: OTPGenerator
var otp: String = {
return generator.generateOTP()
}
init(secretKey: String, issuer: String, scheme: String, createdDate: String) {
self.secretKey = secretKey
self.issuer = issuer
self.scheme = scheme
self.createdDate = createdDate
self.generator = Generator.generatorWithSecretKey(key: secretKey)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(secretKey, forKey: "secretKey")
aCoder.encode(issuer, forKey: "issuer")
aCoder.encode(scheme, forKey: "scheme")
aCoder.encode(createdDate, forKey: "timeInterval")
}
required init?(coder aDecoder: NSCoder) {
secretKey = aDecoder.decodeObject(forKey: "secretKey") as! String
issuer = aDecoder.decodeObject(forKey: "issuer") as! String
scheme = aDecoder.decodeObject(forKey: "scheme") as! String
createdDate = aDecoder.decodeObject(forKey: "timeInterval") as! String
generator = Generator.generatorWithSecretKey(key: secretKey)
}
}
class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource,AddTOTPDelegate, UIGestureRecognizerDelegate {
var tOTPS = [TOTP]()
var secretKeys = [String]()
var timer: Timer?
var currentTimeInterval = TimeInterval()
@IBOutlet weak var tOtpTableView: UITableView!
@IBOutlet weak var btnInfo: UIBarButtonItem!
@IBOutlet weak var btnAddQRCode: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.tOtpTableView.tableFooterView = UIView()
self.emptyDataString = "Click + to add new account"
let longPressGesture:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longPress(_:)))
longPressGesture.minimumPressDuration = 1.0
longPressGesture.delegate = self
self.tOtpTableView.addGestureRecognizer(longPressGesture)
setUpViews()
getTotps()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
if !self.tOTPS.isEmpty {
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(generateTOTP), userInfo: nil, repeats: true)
self.timer?.fire()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getTotps() {
if let decodedData = KeychainWrapper.standard.data(forKey: "tOtps") {
self.tOTPS = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as! [TOTP]
}
}
@IBAction func handleAddQRCode(_ sender: UIButton) {
let controllerToPresent = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "QRScannerController") as! QRScannerController
controllerToPresent.delegate = self
controllerToPresent.tOTPS = self.tOTPS
self.timer?.invalidate()
DispatchQueue.main.async {
self.navigationController?.pushViewController(controllerToPresent, animated: true)
}
}
@objc func generateTOTP() {
tableView.reloadRows(at:tableView.indexPathsForVisibleRows, with:.none)
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.tOTPModel.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "totpCell", for: indexPath) as! TotpViewCell
let tOTP = self.tOTPs[indexPath.section]
cell.lblTOTP.text = tOTP.otp.separate(every: 3, with: " ")
cell.lblIssuer.text = tOTP.issuer
cell.lblCreatedDate.text = "Created Date: \(tOTP.createdDate)"
cell.lblCreatedDate.isHidden = true
cell.issuerConstraint.isActive = true
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
currentTimeInterval = (self.timer?.fireDate.timeIntervalSince(Date()))!
let fromValue = 1
let toValue = 0
cell.progressView.handleAnimation(fromValue: fromValue, tVal: toValue, duration: currentTimeInterval)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: .normal, title: "Edit") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Enter the issuer", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in
let textField = alertController.textFields![0] as UITextField
self.tOTPModel[indexPath.section].issuer = textField.text!
self.tOTPS[indexPath.section].issuer = textField.text!
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.reloadData()
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alertController.addTextField(configurationHandler: {(textField : UITextField!) -> Void in
let tOTP = self.tOTPModel[indexPath.section]
textField.placeholder = "Enter Issuer"
textField.text = tOTP.issuer
})
self.present(alertController, animated: true, completion: nil)
}
editAction.backgroundColor = UIColor(red: 0/255, green: 145/255, blue: 147/255, alpha: 1.0)
let deleteAction = UITableViewRowAction(style: .normal, title: "Delete") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Are you sure you want remove this account?", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: { (action) in
self.tOTPS.remove(at: indexPath.section)
self.tOTPModel.remove(at: indexPath.section)
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.deleteSections([indexPath.section], with: .automatic)
tableView.reloadData()
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: { (action) in
self.dismiss(animated: true, completion: nil)
})
alertController.addAction(cancelAction)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
deleteAction.backgroundColor = .red
return [editAction,deleteAction]
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
// Delegate Method that adds tOtp from QRCode scanner controller
func addTOTP(withSecret secret: String, issuer: String, scheme: String,createdDate: String) {
self.tOTPS.append(TOTP(secretKey: secret, issuer: issuer, scheme: scheme, createdDate: createdDate))
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
}
}
我正在开发一个使用计时器每 30 秒生成一次顶部的应用程序。我从扫描的二维码中获取密钥,并将它们附加到一个模型数组中,对于每个密钥,我生成 tOTP 并将它们附加到另一个模型数组中。使用该模型数组,我正在填充表视图。
tableviewcell 包含用于显示 otp 的标签和用于跟踪进度的自定义循环进度视图。主要问题是我无法与 Google Authenticator 等其他 TOTP 应用保持同步。当我每秒 运行 计时器时,我可以每秒从 TOTP 生成器库生成 otps,并且可以通过重新加载 tableview 来更新标签。但是此功能会影响进度视图,删除编辑表视图单元格,因为我每秒 运行ning 计时器生成 otp 并重新加载表视图。 希望有人帮忙...
这是我的代码...
class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource,AddTOTPDelegate, UIGestureRecognizerDelegate {
var tOTPS = [TOTP]()
var tOTPModel = [TOTP]()
var secretKeys = [String]()
var generator: OTPGenerator?
var timer: Timer?
var currentTimeInterval = TimeInterval()
@IBOutlet weak var tOtpTableView: UITableView!
@IBOutlet weak var btnInfo: UIBarButtonItem!
@IBOutlet weak var btnAddQRCode: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.tOtpTableView.tableFooterView = UIView()
self.emptyDataString = "Click + to add new account"
let longPressGesture:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longPress(_:)))
longPressGesture.minimumPressDuration = 1.0
longPressGesture.delegate = self
self.tOtpTableView.addGestureRecognizer(longPressGesture)
setUpViews()
getTotps()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
if self.tOTPS.isEmpty == false {
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(generateTOTP), userInfo: nil, repeats: true)
self.timer?.fire()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getTotps() {
if let decodedData = KeychainWrapper.standard.data(forKey: "tOtps") {
self.tOTPS = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as! [TOTP]
}
}
@IBAction func handleAddQRCode(_ sender: UIButton) {
let controllerToPresent = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "QRScannerController") as! QRScannerController
controllerToPresent.delegate = self
controllerToPresent.tOTPS = self.tOTPS
self.timer?.invalidate()
DispatchQueue.main.async {
self.navigationController?.pushViewController(controllerToPresent, animated: true)
}
}
@objc func generateTOTP() {
self.tOTPModel = []
for tOtpObject in self.tOTPS {
self.generator = Generator.generatorWithSecretKey(key: tOtpObject.secretKey)
let tOtp = (self.generator as! TOTPGenerator).generateOTP()
self.tOTPModel.append(TOTP(secretKey: tOtp!, issuer: tOtpObject.issuer, scheme: tOtpObject.scheme, createdDate: tOtpObject.createdDate))
}
self.tOtpTableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.tOTPModel.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "totpCell", for: indexPath) as! TotpViewCell
let tOTP = self.tOTPModel[indexPath.section]
cell.lblTOTP.text = tOTP.secretKey.separate(every: 3, with: " ")
cell.lblIssuer.text = tOTP.issuer
cell.lblCreatedDate.text = "Created Date: \(tOTP.createdDate)"
cell.lblCreatedDate.isHidden = true
cell.issuerConstraint.isActive = true
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
currentTimeInterval = (self.timer?.fireDate.timeIntervalSince(Date()))!
let fromValue = 1
let toValue = 0
cell.progressView.handleAnimation(fromValue: fromValue, tVal: toValue, duration: currentTimeInterval)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: .normal, title: "Edit") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Enter the issuer", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in
let textField = alertController.textFields![0] as UITextField
self.tOTPModel[indexPath.section].issuer = textField.text!
self.tOTPS[indexPath.section].issuer = textField.text!
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.reloadData()
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alertController.addTextField(configurationHandler: {(textField : UITextField!) -> Void in
let tOTP = self.tOTPModel[indexPath.section]
textField.placeholder = "Enter Issuer"
textField.text = tOTP.issuer
})
self.present(alertController, animated: true, completion: nil)
}
editAction.backgroundColor = UIColor(red: 0/255, green: 145/255, blue: 147/255, alpha: 1.0)
let deleteAction = UITableViewRowAction(style: .normal, title: "Delete") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Are you sure you want remove this account?", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: { (action) in
self.tOTPS.remove(at: indexPath.section)
self.tOTPModel.remove(at: indexPath.section)
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.deleteSections([indexPath.section], with: .automatic)
tableView.reloadData()
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: { (action) in
self.dismiss(animated: true, completion: nil)
})
alertController.addAction(cancelAction)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
deleteAction.backgroundColor = .red
return [editAction,deleteAction]
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
// Delegate Method that adds tOtp from QRCode scanner controller
func addTOTP(withSecret secret: String, issuer: String, scheme: String,createdDate: String) {
self.tOTPS.append(TOTP(secretKey: secret, issuer: issuer, scheme: scheme, createdDate: createdDate))
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
}
}
这是模型对象...
class TOTP: NSObject, NSCoding {
var secretKey: String
var issuer: String
var scheme: String
var createdDate: String
init(secretKey: String, issuer: String, scheme: String, createdDate: String) {
self.secretKey = secretKey
self.issuer = issuer
self.scheme = scheme
self.createdDate = createdDate
}
func encode(with aCoder: NSCoder) {
aCoder.encode(secretKey, forKey: "secretKey")
aCoder.encode(issuer, forKey: "issuer")
aCoder.encode(scheme, forKey: "scheme")
aCoder.encode(createdDate, forKey: "timeInterval")
}
required init?(coder aDecoder: NSCoder) {
secretKey = aDecoder.decodeObject(forKey: "secretKey") as! String
issuer = aDecoder.decodeObject(forKey: "issuer") as! String
scheme = aDecoder.decodeObject(forKey: "scheme") as! String
createdDate = aDecoder.decodeObject(forKey: "timeInterval") as! String
}
}
这是生成 TOTP 的生成器 class...
class Generator {
static func generatorWithSecretKey(key: String) -> OTPGenerator {
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
let secretKey = MF_Base32Codec.data(fromBase32String: key)
return TOTPGenerator(secret: secretKey, algorithm: OTPGenerator.defaultAlgorithm(), digits: 6, period: 30)
}
}
我不明白你为什么需要单独的 tOTPModel
数组。我会删除它并只使用 tOTPS
数组,我会将生成器放在它所属的 TOTP
对象中。
现在您可以在每次计时器计时时重新加载可见行。
class TOTP: NSObject, NSCoding {
var secretKey: String
var issuer: String
var scheme: String
var createdDate: String
private var generator: OTPGenerator
var otp: String = {
return generator.generateOTP()
}
init(secretKey: String, issuer: String, scheme: String, createdDate: String) {
self.secretKey = secretKey
self.issuer = issuer
self.scheme = scheme
self.createdDate = createdDate
self.generator = Generator.generatorWithSecretKey(key: secretKey)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(secretKey, forKey: "secretKey")
aCoder.encode(issuer, forKey: "issuer")
aCoder.encode(scheme, forKey: "scheme")
aCoder.encode(createdDate, forKey: "timeInterval")
}
required init?(coder aDecoder: NSCoder) {
secretKey = aDecoder.decodeObject(forKey: "secretKey") as! String
issuer = aDecoder.decodeObject(forKey: "issuer") as! String
scheme = aDecoder.decodeObject(forKey: "scheme") as! String
createdDate = aDecoder.decodeObject(forKey: "timeInterval") as! String
generator = Generator.generatorWithSecretKey(key: secretKey)
}
}
class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource,AddTOTPDelegate, UIGestureRecognizerDelegate {
var tOTPS = [TOTP]()
var secretKeys = [String]()
var timer: Timer?
var currentTimeInterval = TimeInterval()
@IBOutlet weak var tOtpTableView: UITableView!
@IBOutlet weak var btnInfo: UIBarButtonItem!
@IBOutlet weak var btnAddQRCode: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.tOtpTableView.tableFooterView = UIView()
self.emptyDataString = "Click + to add new account"
let longPressGesture:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longPress(_:)))
longPressGesture.minimumPressDuration = 1.0
longPressGesture.delegate = self
self.tOtpTableView.addGestureRecognizer(longPressGesture)
setUpViews()
getTotps()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
if !self.tOTPS.isEmpty {
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(generateTOTP), userInfo: nil, repeats: true)
self.timer?.fire()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getTotps() {
if let decodedData = KeychainWrapper.standard.data(forKey: "tOtps") {
self.tOTPS = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as! [TOTP]
}
}
@IBAction func handleAddQRCode(_ sender: UIButton) {
let controllerToPresent = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "QRScannerController") as! QRScannerController
controllerToPresent.delegate = self
controllerToPresent.tOTPS = self.tOTPS
self.timer?.invalidate()
DispatchQueue.main.async {
self.navigationController?.pushViewController(controllerToPresent, animated: true)
}
}
@objc func generateTOTP() {
tableView.reloadRows(at:tableView.indexPathsForVisibleRows, with:.none)
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.tOTPModel.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "totpCell", for: indexPath) as! TotpViewCell
let tOTP = self.tOTPs[indexPath.section]
cell.lblTOTP.text = tOTP.otp.separate(every: 3, with: " ")
cell.lblIssuer.text = tOTP.issuer
cell.lblCreatedDate.text = "Created Date: \(tOTP.createdDate)"
cell.lblCreatedDate.isHidden = true
cell.issuerConstraint.isActive = true
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
currentTimeInterval = (self.timer?.fireDate.timeIntervalSince(Date()))!
let fromValue = 1
let toValue = 0
cell.progressView.handleAnimation(fromValue: fromValue, tVal: toValue, duration: currentTimeInterval)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: .normal, title: "Edit") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Enter the issuer", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in
let textField = alertController.textFields![0] as UITextField
self.tOTPModel[indexPath.section].issuer = textField.text!
self.tOTPS[indexPath.section].issuer = textField.text!
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.reloadData()
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alertController.addTextField(configurationHandler: {(textField : UITextField!) -> Void in
let tOTP = self.tOTPModel[indexPath.section]
textField.placeholder = "Enter Issuer"
textField.text = tOTP.issuer
})
self.present(alertController, animated: true, completion: nil)
}
editAction.backgroundColor = UIColor(red: 0/255, green: 145/255, blue: 147/255, alpha: 1.0)
let deleteAction = UITableViewRowAction(style: .normal, title: "Delete") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Are you sure you want remove this account?", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: { (action) in
self.tOTPS.remove(at: indexPath.section)
self.tOTPModel.remove(at: indexPath.section)
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.deleteSections([indexPath.section], with: .automatic)
tableView.reloadData()
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: { (action) in
self.dismiss(animated: true, completion: nil)
})
alertController.addAction(cancelAction)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
deleteAction.backgroundColor = .red
return [editAction,deleteAction]
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
// Delegate Method that adds tOtp from QRCode scanner controller
func addTOTP(withSecret secret: String, issuer: String, scheme: String,createdDate: String) {
self.tOTPS.append(TOTP(secretKey: secret, issuer: issuer, scheme: scheme, createdDate: createdDate))
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
}
}