Merhaba, bu yazıda sizlere C# ile Google Authenticator / Two Factor Authenticator Kullanımı hakkında bilgi vereceğim.
Yaptığınız uygulamalarda bazen güvenlik önlemlerine ihtiyaç duyabilirsiniz. Bunun için Google Authenticator / Two Factor Authenticator’ı kullanabilirsiniz. En bilindik güvenlik yöntemi SMS ile doğrulamadır. Banka uygulamalarında bunu sıkça görebilirsiniz. Telefonunuza gönderilen süreli kod ile giriş sağlayabiliyorsunuz. Sms’e alternatif olarak Google tarafından geliştirilen uygulamayı kullanarak 2 aşamalı doğrulama yapabilirsiniz. Şuan en çok sanal para işlemlerinde kullanılmaktadır. Ufak bir uygulama yapacağız ve yaptığımız örnek uygulama linkini de anlatımımın sonuna ekleyeceğim.
Google Authenticator / Two Factor Authenticator Nerede İşimize Yarar ?
Ticari bir yazılım yaptığınızı düşünelim. Müşterilerin giriş bilgileri başka kişilerin ellerine geçebilir. Bunu ortadan kaldırmak için bu 2 aşamalı doğrulama sistemini programınızın giriş kısmına koyabilirsiniz. Daha farklı uygulamalarda farklı alanlarda da kullanabilirsiniz. Web uygulamalarında güvenlik için veya başka durumlar için kullanabilirsiniz.
Proje Kodlarının Eklenmesi
Yavaş yavaş proje bölümümüze geçelim. NuGet ile Google Authenticator – Brandon Potter’ de yapabilirsiniz. Ancak ben bugün Class yardımıyla yapmak istiyorum.
SetupCode adında Public bir class oluşturuyorum. Class oluşturmak için Solution Explorer – Sağ Tık – Add – Class adımlarını izleyerek oluşturabilirsiniz.
1 2 3 4 5 6 7 | public class SetupCode { public string Account { get; internal set; } public string AccountSecretKey { get; internal set; } public string ManualEntryKey { get; internal set; } public string QrCodeSetupImageUrl { get; internal set; } } |
Daha sonra TwoFactorAuthenticator adında Public bir class oluşturuyorum. Bu adımları tek tek anlatmayacağım. Kopyala yapıştır yapabilirsiniz.
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | public class TwoFactorAuthenticator { public static DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public TimeSpan DefaultClockDriftTolerance { get; set; } public bool UseManagedSha1Algorithm { get; set; } public bool TryUnmanagedAlgorithmOnFailure { get; set; } public TwoFactorAuthenticator() : this(true, true) { } public TwoFactorAuthenticator(bool useManagedSha1, bool useUnmanagedOnFail) { DefaultClockDriftTolerance = TimeSpan.FromMinutes(5); UseManagedSha1Algorithm = useManagedSha1; TryUnmanagedAlgorithmOnFailure = useUnmanagedOnFail; } public SetupCode GenerateSetupCode(string accountTitleNoSpaces, string accountSecretKey, int qrCodeWidth, int qrCodeHeight) { return GenerateSetupCode(null, accountTitleNoSpaces, accountSecretKey, qrCodeWidth, qrCodeHeight); } public SetupCode GenerateSetupCode(string issuer, string accountTitleNoSpaces, string accountSecretKey, int qrCodeWidth, int qrCodeHeight) { return GenerateSetupCode(issuer, accountTitleNoSpaces, accountSecretKey, qrCodeWidth, qrCodeHeight, false); } public SetupCode GenerateSetupCode(string issuer, string accountTitleNoSpaces, string accountSecretKey, int qrCodeWidth, int qrCodeHeight, bool useHttps) { if (accountTitleNoSpaces == null) { throw new NullReferenceException("Account Title is null"); } accountTitleNoSpaces = accountTitleNoSpaces.Replace(" ", ""); SetupCode sC = new SetupCode(); sC.Account = accountTitleNoSpaces; sC.AccountSecretKey = accountSecretKey; string encodedSecretKey = EncodeAccountSecretKey(accountSecretKey); sC.ManualEntryKey = encodedSecretKey; string provisionUrl = null; if (string.IsNullOrEmpty(issuer)) { provisionUrl = UrlEncode(String.Format("otpauth://totp/{0}?secret={1}", accountTitleNoSpaces, encodedSecretKey)); } else { provisionUrl = UrlEncode(String.Format("otpauth://totp/{0}?secret={1}&issuer={2}", accountTitleNoSpaces, encodedSecretKey, UrlEncode(issuer))); } string protocol = useHttps ? "https" : "http"; string url = String.Format("{0}://chart.googleapis.com/chart?cht=qr&chs={1}x{2}&chl={3}", protocol, qrCodeWidth, qrCodeHeight, provisionUrl); sC.QrCodeSetupImageUrl = url; return sC; } private string UrlEncode(string value) { StringBuilder result = new StringBuilder(); string validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; foreach (char symbol in value) { if (validChars.IndexOf(symbol) != -1) { result.Append(symbol); } else { result.Append('%' + String.Format("{0:X2}", (int)symbol)); } } return result.ToString().Replace(" ", "%20"); } private string EncodeAccountSecretKey(string accountSecretKey) { return Base32Encode(Encoding.UTF8.GetBytes(accountSecretKey)); } private string Base32Encode(byte[] data) { int inByteSize = 8; int outByteSize = 5; char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".ToCharArray(); int i = 0, index = 0, digit = 0; int current_byte, next_byte; StringBuilder result = new StringBuilder((data.Length + 7) * inByteSize / outByteSize); while (i < data.Length) { current_byte = (data[i] >= 0) ? data[i] : (data[i] + 256); if (index > (inByteSize - outByteSize)) { if ((i + 1) < data.Length) next_byte = (data[i + 1] >= 0) ? data[i + 1] : (data[i + 1] + 256); else next_byte = 0; digit = current_byte & (0xFF >> index); index = (index + outByteSize) % inByteSize; digit <<= index; digit |= next_byte >> (inByteSize - index); i++; } else { digit = (current_byte >> (inByteSize - (index + outByteSize))) & 0x1F; index = (index + outByteSize) % inByteSize; if (index == 0) i++; } result.Append(alphabet[digit]); } return result.ToString(); } public string GeneratePINAtInterval(string accountSecretKey, long counter, int digits = 6) { return GenerateHashedCode(accountSecretKey, counter, digits); } internal string GenerateHashedCode(string secret, long iterationNumber, int digits = 6) { byte[] key = Encoding.UTF8.GetBytes(secret); return GenerateHashedCode(key, iterationNumber, digits); } internal string GenerateHashedCode(byte[] key, long iterationNumber, int digits = 6) { byte[] counter = BitConverter.GetBytes(iterationNumber); if (BitConverter.IsLittleEndian) { Array.Reverse(counter); } HMACSHA1 hmac = getHMACSha1Algorithm(key); byte[] hash = hmac.ComputeHash(counter); int offset = hash[hash.Length - 1] & 0xf; int binary = ((hash[offset] & 0x7f) << 24) | (hash[offset + 1] << 16) | (hash[offset + 2] << 8) | (hash[offset + 3]); int password = binary % (int)Math.Pow(10, digits); return password.ToString(new string('0', digits)); } private long GetCurrentCounter() { return GetCurrentCounter(DateTime.UtcNow, _epoch, 30); } private long GetCurrentCounter(DateTime now, DateTime epoch, int timeStep) { return (long)(now - epoch).TotalSeconds / timeStep; } private HMACSHA1 getHMACSha1Algorithm(byte[] key) { HMACSHA1 hmac; try { hmac = new HMACSHA1(key, UseManagedSha1Algorithm); } catch (InvalidOperationException ioe) { if (UseManagedSha1Algorithm && TryUnmanagedAlgorithmOnFailure) { try { hmac = new HMACSHA1(key, false); } catch (InvalidOperationException ioe2) { throw ioe2; } } else { throw ioe; } } return hmac; } public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient) { return ValidateTwoFactorPIN(accountSecretKey, twoFactorCodeFromClient, DefaultClockDriftTolerance); } public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient, TimeSpan timeTolerance) { var codes = GetCurrentPINs(accountSecretKey, timeTolerance); return codes.Any(c => c == twoFactorCodeFromClient); } public string GetCurrentPIN(string accountSecretKey) { return GeneratePINAtInterval(accountSecretKey, GetCurrentCounter()); } public string GetCurrentPIN(string accountSecretKey, DateTime now) { return GeneratePINAtInterval(accountSecretKey, GetCurrentCounter(now, _epoch, 30)); } public string[] GetCurrentPINs(string accountSecretKey) { return GetCurrentPINs(accountSecretKey, DefaultClockDriftTolerance); } public string[] GetCurrentPINs(string accountSecretKey, TimeSpan timeTolerance) { List<string> codes = new List<string>(); long iterationCounter = GetCurrentCounter(); int iterationOffset = 0; if (timeTolerance.TotalSeconds > 30) { iterationOffset = Convert.ToInt32(timeTolerance.TotalSeconds / 30.00); } long iterationStart = iterationCounter - iterationOffset; long iterationEnd = iterationCounter + iterationOffset; for (long counter = iterationStart; counter <= iterationEnd; counter++) { codes.Add(GeneratePINAtInterval(accountSecretKey, counter)); } return codes.ToArray(); } } |
Form tarafında 3 adet Button, 3 Label, 1 Picturebox, 5 Textbox kullandım. Zaten kodları okumaya başlayınca anlayacaksınız.
Formumuzun load kısmında hesabımızın başlığını oluşturuyoruz. Mobil uygulamada gözükecek başlıktır. Daha sonra Google tarafından verilen key’i yazıyoruz. Bu paylaştığım key çalışmaktadır. Key’i kullanabilirsiniz.
1 2 3 4 5 | private void Form1_Load(object sender, EventArgs e) { this.txtAccountTitle.Text = "https://www.karadasburak.com/"; this.txtSecretKey.Text = "f68f1fe894d548a1bbc66165c46e61eb"; } |
Setup kısmı mobil uygulamamıza karekod olarak okutacağımız bölümdür. Mobil uygulamamıza okuttuktan sonra artık bu hesap üzerinden devam etmektedir.
1 2 3 4 5 6 7 8 9 10 11 12 13 | private void btnSetup_Click(object sender, EventArgs e) { TwoFactorAuthenticator tfA = new TwoFactorAuthenticator(); var setupCode = tfA.GenerateSetupCode(this.txtAccountTitle.Text, this.txtSecretKey.Text, pbQR.Width, pbQR.Height); WebClient wc = new WebClient(); MemoryStream ms = new MemoryStream(wc.DownloadData(setupCode.QrCodeSetupImageUrl)); this.pbQR.Image = Image.FromStream(ms); this.txtSetupCode.Text = "Account: " + setupCode.Account + System.Environment.NewLine + "Secret Key: " + setupCode.AccountSecretKey + System.Environment.NewLine + "Encoded Key: " + setupCode.ManualEntryKey; } |
Belli bir sürede değişine kodun test edilmesi için ben bir mesaj döndürdüm.
1 2 3 | TwoFactorAuthenticator tfA = new TwoFactorAuthenticator(); var result = tfA.ValidateTwoFactorPIN(txtSecretKey.Text, this.txtCode.Text); MessageBox.Show(result ? "Validated!" : "Incorrect", "Result"); |
Sıradaki gelecek kodları listeyebilirsiniz. Bunun için aşağıdaki kodu projenize ekleyebilirsiniz.
1 2 3 4 | private void btnGetCurrentCode_Click(object sender, EventArgs e) { this.txtCurrentCodes.Text = string.Join(System.Environment.NewLine, new TwoFactorAuthenticator().GetCurrentPINs(this.txtSecretKey.Text)); } |
Projenin kaynak dosyalarını aşağıdan indirebilirsiniz.
Soru ve görüşleriniz için [email protected] adresine mail atabilirsiniz.