什麼是Windows Hellogit
Windows Hello是windows 10 中提供的使用生物識別來解鎖windows的一個框架。github
若是你擁有人臉識別的攝像頭或者指紋識別器亦或者是windows 手環的話是能夠在系統中直接開啓的。windows
事情的原由session
有一次看 「科技美學」 的時候的 那巖提到windows紅石更新以後 可使用小米手環解鎖小米的筆記本,我立馬意識到這可能就是使用了Windows Hello 框架。app
可是前些日子比較忙,最近纔開始有空好好探討一下。框架
目標dom
我手頭有一個小米手環2,因此個人目標就是當筆記本檢測到個人小米手環的時候能夠直接解鎖個人Windows。async
項目地址: http://git.oschina.net/alwaysking/MyHelloWinide
Let's Start函數
第一件事情天然就是google相關的內容啦,我找到一個windows 的例子
Demo: https://github.com/Microsoft/companion-device-framework/tree/master/CDFSampleApp
找到了這個例子,其實Windows Hello的使用方法就已經所有展示在眼前了。
我簡單介紹一下這個項目的結構,這是一個UWP應用
其中包含兩個項目。
CDFSampleApp是界面應用
包含三個功能對應三個按鈕
1. 註冊設備
2. 啓動後臺應用
3. 註銷設備
啓動後臺應用啓動的就是第二個項目Tasks
Task的做用就是接受系統的通知來使用Windows Hello 解鎖Windows
接下分析一下具體的代碼實現
主要是經過 SecondaryAuthenticationFactorRegistration 類來實現功能的,
1. 首先註冊一個用於解鎖Windows的虛擬設備,這個設備是和Windows帳號綁定的
// 個人代碼爲了演示的精簡,刪除輔助和錯誤判斷的代碼,使用的時候請參考源代碼。 // 生成設備的ID,用於惟一表示一個設備 String deviceId = System.Guid.NewGuid().ToString(); // 隨機生成了密碼 IBuffer deviceKey = CryptographicBuffer.GenerateRandom(32); IBuffer authKey = CryptographicBuffer.GenerateRandom(32); // 設備的相關名稱 String deviceFriendlyName = "Test Simulator"; String deviceModelNumber = "Sample A1"; // 註冊設備 SecondaryAuthenticationFactorDeviceCapabilities capabilities = SecondaryAuthenticationFactorDeviceCapabilities.SecureStorage; SecondaryAuthenticationFactorRegistrationResult registrationResult = await SecondaryAuthenticationFactorRegistration.RequestStartRegisteringDeviceAsync(deviceId, capabilities, deviceFriendlyName, deviceModelNumber, deviceKey, authKey); // 完成設備的註冊 // IBuffer deviceConfigData; // 示例爲了方便,他將 deviceKey authKey 存儲到了 deviceConfigData 中 await registrationResult.Registration.FinishRegisteringDeviceAsync(deviceConfigData);
2. 啓動後臺任務,這個後臺任務就是實際完成解鎖的程序,她將在windows正在解鎖的時候收到通知。
// 建立後臺時間註冊類 BackgroundTaskBuilder taskBuilder = new BackgroundTaskBuilder(); // 後臺事件的名稱 taskBuilder.Name = myBGTaskName; // 這是一個觸發器,這個觸發器決定了,windows在是時候運行這個後程序 // 顯然這個程序就是指定在windows須要進行Windows Hello 認證的時候觸發啦 SecondaryAuthenticationFactorAuthenticationTrigger myTrigger = new SecondaryAuthenticationFactorAuthenticationTrigger(); // 任務的入口點 taskBuilder.TaskEntryPoint = "Tasks.myBGTask"; // 將觸發器註冊到任務 taskBuilder.SetTrigger(myTrigger); // 向windows註冊任務 BackgroundTaskRegistration taskReg = taskBuilder.Register();
完成上面的操做以後Windows就會在解鎖的使用啓動你的Task。
3. 編寫你的Task
// 你的任務的入口類必須繼承自IBackgroundTask // 而且重載public void Run(IBackgroundTaskInstance taskInstance)接口 public sealed class myBGTask : IBackgroundTask { public void Run(IBackgroundTaskInstance taskInstance) { 。。。。。。 } }
4. 處理Windows解鎖事件
public void Run(IBackgroundTaskInstance taskInstance) { // 標記須要延遲完成,不然這個程序可能還沒結束就會被Windows強X? var deferral = taskInstance.GetDeferral(); // 建立一個事件,當功能完成的時候事件會被觸發 opCompletedEvent = new ManualResetEvent(false); // 註冊用於接受Windows解鎖變化的函數 SecondaryAuthenticationFactorAuthentication.AuthenticationStageChanged += OnStageChanged; // 等待事件的完成 opCompletedEvent.WaitOne(); // 通知windows這個Task完成了,這個實現這個程序會被掛起 deferral.Complete(); } async void OnStageChanged(Object sender, SecondaryAuthenticationFactorAuthenticationStageChangedEventArgs args) { // 進入鎖屏狀態的時候 if (args.StageInfo.Stage == SecondaryAuthenticationFactorAuthenticationStage.WaitingForUserConfirmation) { // 經過這個函數你能夠在鎖屏界面顯示你想給出的提示內容 // 這個提示內容是定製的,你只能自定義一部份 // 使用什麼樣子的格式你能夠經過改變枚舉SecondaryAuthenticationFactorAuthenticationMessage的值來控制 String deviceName = "自定義的提示內容"; await SecondaryAuthenticationFactorAuthentication.ShowNotificationMessageAsync( deviceName, SecondaryAuthenticationFactorAuthenticationMessage.SwipeUpWelcome); } else if (args.StageInfo.Stage == SecondaryAuthenticationFactorAuthenticationStage.CollectingCredential) { // 正在解鎖的中 PerformAuthentication(); } else { // 其餘狀況 if (args.StageInfo.Stage == SecondaryAuthenticationFactorAuthenticationStage.StoppingAuthentication) { SecondaryAuthenticationFactorAuthentication.AuthenticationStageChanged -= OnStageChanged; opCompletedEvent.Set(); } SecondaryAuthenticationFactorAuthenticationStage stage = args.StageInfo.Stage; } }
5. 解鎖設備
//Get the selected device from app settings var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings; String m_selectedDeviceId = localSettings.Values["SelectedDevice"] as String; // 再次判斷當前狀態 SecondaryAuthenticationFactorAuthenticationStageInfo authStageInfo = await SecondaryAuthenticationFactorAuthentication.GetAuthenticationStageInfoAsync(); if (authStageInfo.Stage != SecondaryAuthenticationFactorAuthenticationStage.CollectingCredential) { throw new Exception("Unexpected!"); } // 獲取全部註冊的設備 IReadOnlyList <SecondaryAuthenticationFactorInfo> deviceList = await SecondaryAuthenticationFactorRegistration.FindAllRegisteredDeviceInfoAsync( SecondaryAuthenticationFactorDeviceFindScope.AllUsers); if (deviceList.Count == 0) { throw new Exception ("Unexpected exception, device list = 0"); } // 獲取第一個註冊的設備 SecondaryAuthenticationFactorInfo deviceInfo = deviceList.ElementAt(0); m_selectedDeviceId = deviceInfo.DeviceId; //a nonce is an arbitrary number that may only be used once - a random or pseudo-random number issued in an authentication protocol to ensure that old communications cannot be reused in replay attacks. // 計算一個通信用的密碼,用於防止被重播攻擊等 IBuffer svcNonce = CryptographicBuffer.GenerateRandom(32); //Generate a nonce and do a HMAC operation with the nonce //In real world, you would need to take this nonce and send to companion device to perform an HMAC operation with it //You will have only 20 second to get the HMAC from the companion device // 標記開始識別了,這個時候解鎖界面會顯示一個笑臉的解鎖標誌 SecondaryAuthenticationFactorAuthenticationResult authResult = await SecondaryAuthenticationFactorAuthentication.StartAuthenticationAsync( m_selectedDeviceId, svcNonce); if (authResult.Status != SecondaryAuthenticationFactorAuthenticationStatus.Started) { throw new Exception("Unexpected! Could not start authentication!"); } // 這裏須要使用註冊時候的密碼 IBuffer deviceKey = 。。。; IBuffer authKey = 。。。; // 接下來是一些列密碼加密過程 // Calculate the HMAC MacAlgorithmProvider hMACSha256Provider = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256); CryptographicKey deviceHmacKey = hMACSha256Provider.CreateKey(deviceKey); IBuffer deviceHmac = CryptographicEngine.Sign(deviceHmacKey, authResult.Authentication.DeviceNonce); // sessionHmac = HMAC(authKey, deviceHmac || sessionNonce) IBuffer sessionHmac; byte[] deviceHmacArray = { 0 }; CryptographicBuffer.CopyToByteArray(deviceHmac, out deviceHmacArray); byte[] sessionNonceArray = { 0 }; CryptographicBuffer.CopyToByteArray(authResult.Authentication.SessionNonce, out sessionNonceArray); byte[] combinedDataArray = new byte[deviceHmacArray.Length + sessionNonceArray.Length]; for (int index = 0; index < deviceHmacArray.Length; index++) { combinedDataArray[index] = deviceHmacArray[index]; } for (int index = 0; index < sessionNonceArray.Length; index++) { combinedDataArray[deviceHmacArray.Length + index] = sessionNonceArray[index]; } // Get a Ibuffer from combinedDataArray IBuffer sessionMessage = CryptographicBuffer.CreateFromByteArray(combinedDataArray); // Calculate sessionHmac CryptographicKey authHmacKey = hMACSha256Provider.CreateKey(authKey); sessionHmac = CryptographicEngine.Sign(authHmacKey, sessionMessage); // 而後使用密碼去嘗試解鎖 SecondaryAuthenticationFactorFinishAuthenticationStatus authStatus = await authResult.Authentication.FinishAuthenticationAsync(deviceHmac, sessionHmac); if (authStatus != SecondaryAuthenticationFactorFinishAuthenticationStatus.Completed) { throw new Exception("Unable to complete authentication!"); }
完整的流程就是這樣。
其中有幾個須要解釋的點
可使用以下代碼來獲取全部已註冊的設備
IReadOnlyList<SecondaryAuthenticationFactorInfo> deviceList = await SecondaryAuthenticationFactorRegistration.FindAllRegisteredDeviceInfoAsync(SecondaryAuthenticationFactorDeviceFindScope.User);
其中SecondaryAuthenticationFactorInfo的定義以下
// // 摘要: // 包含提供有關配套設備的信息的屬性。 [ContractVersion(typeof(UniversalApiContract), 196608)] [DualApiPartition(version = 167772162)] [MarshalingBehavior(MarshalingType.Agile)] [Threading(ThreadingModel.Both)] public sealed class SecondaryAuthenticationFactorInfo : ISecondaryAuthenticationFactorInfo { // // 摘要: // 獲取設備配置數據。 // // 返回結果: // 設備配置數據。 public IBuffer DeviceConfigurationData { get; } // // 摘要: // 獲取設備的友好名稱。 // // 返回結果: // 設備的友好名稱。 public string DeviceFriendlyName { get; } // // 摘要: // 獲取設備 ID。 // // 返回結果: // 設備 ID。 public string DeviceId { get; } // // 摘要: // 獲取設備型號。 // // 返回結果: // 設備型號。 public string DeviceModelNumber { get; } }
這些參數都是在註冊的時候就指定了的。而其中的 DeviceConfigurationData 參數是能夠在後期使用以下函數進行更新。
SecondaryAuthenticationFactorRegistration.UpdateDeviceConfigurationDataAsync(string deviceId, IBuffer deviceConfigurationData);
這個例子中Windows爲了演示方便就是直接使用 DeviceConfigurationData 變量存儲了 兩個密碼DeviceKey 和 AuthKey.
還有一點就是註冊的時候的DevieID,必須是GUID。
好的,這個例子就暫時先分析道這裏