Tek Kullanımlık Şifre (One-time Password) Uygulaması

Tek Kullanımlık Şifre (One-time Password) Uygulaması Bu makalemizde tek zamanlı şifre mekanizmasının Microsoft .NET teknolojileriyle nasıl gerçeklenebileceğini inceleyeceğiz. Zorluk Derecesi: 1 2 3 4 5 Anahtar Sözcükler: Microsoft .NET, One Time Password, Tek Kullanımlık Şifre, Şifre Üretici, Şifrematik OTP uygulaması aslında bir servis olarak geliştirilmesi gereken bir uygulamadır.Ancak bu kararı size bırakabilmek için sadece sınıf geliştirmelerini yapacağız.Bu sınıfı istediğiniz bir uygulamadan çağırabilirsiniz. Örnek olması için buradabir konsol uygulaması geliştireceğiz.VS.NET kullanarak yeni bir Console Application başlatalım. Solution Explorer yardımıylaPasswordGenerator isminde yeni bir sınıf ekleyelim. Bu sınıfı singleton design pattern"inikullanarak oluşturalım. Ayrıca bu sınıfı mühürlü olarak işaretlememiz de yararlıolacaktır.Rastgele sayı üretimi için "RNGCryptoServiceProvider" sınıfını, hash fonksiyonuolarak da SHA1 kullanacağımız için SHA1Managed sınıfını bu sınıfımıza üye olarakekleyelim.C#sealed class PasswordGenerator { #region private member declarations private static object synObject = new object(); private static PasswordGenerator _current; private RNGCryptoServiceProvider rngProvider; private SHA1Managed hashAlgorithm; #endregion #region property definitions public static PasswordGenerator Current { get { if (_current == null) { lock (synObject) { if (_current == null) _current = new PasswordGenerator(); } } return _current; } } public int PasswordLength { get { return 20; } } #endregion #region constructor definition internal PasswordGenerator() { rngProvider = new RNGCryptoServiceProvider(); hashAlgorithm = new SHA1Managed(); } #endregion }VB.NET _ Public NotInheritable Class PasswordGenerator #Region "Private member declarations" Private Shared synObject As New Object Private Shared _current As PasswordGenerator Private rngProvider As RNGCryptoServiceProvider Private hashAlgorithm As SHA1Managed #End Region #Region "property definitions" Public Shared ReadOnly Property Current() As PasswordGenerator Get If _current Is Nothing Then SyncLock (synObject) If _current Is Nothing Then _current = New PasswordGenerator() End If End SyncLock End If Return _current End Get End Property Public ReadOnly Property PasswordLength() As Integer Get Return 20 End Get End Property #End Region #Region "constructor definition" Friend Sub PasswordGenerator() rngProvider = New RNGCryptoServiceProvider() hashAlgorithm = New SHA1Managed() End Sub #End Region End ClassC++.NETpublic ref class PasswordGenerator { private: static Object synObject; static PasswordGenerator^ _current; RNGCryptoServiceProvider rngProvider; SHA1Managed hashAlgorithm; public: static property PasswordGenerator^ Current { public: PasswordGenerator^ get() { if (_current ==nullptr) { System::Threading::Mutex m; m.WaitOne(); if (_current==nullptr) _current = gcnew PasswordGenerator(); } return _current; } } property int PasswordLength { public: int get() { return 20; } } private: PasswordGenerator(){} }Şifre uzunluğu olarak 20 kullanılmasının özel bir sebebi vardır: Hatırlayacağınızüzere SHA1 160 bit hashing yapmaktadır. Bu yüzden şifre uzunluğumuz 160/8 = 20 karakterolacaktır. Bu sayıyı SHA512 kullanarak 64 karaktere kadar da çıkartabiliriz. Ancakbu uygulama için 20 karakter uzunluğunda bir şifre seçimi yeterli olacaktır. Ayrıcaşifre seçimi makalesine tekrar bakarsanız 20 karakter uzunluğundaki bir şifre oldukçagüvenli sayılabileceğini düşünebiliriz.Üreteceğimiz şifrelerde kullanılacak olan karakterlerin tanımlı olduğu bir alfabesınıfına ihtiyacımız olacaktır. Alfabe sınıfını bu sınıfımızın içine inner classolarak tanımlayalım:C##region internal type declarations internal class PasswordAlphabet { #region private member declarations private static PasswordAlphabet _current; private static object synObject=new object(); private char[] alphabetBuffer; private int upperBound = 0; #endregion #region public properties public static PasswordAlphabet Current { get { if (_current==null) { lock (synObject) { if (_current == null) { _current = new PasswordAlphabet(); } } } return _current; } } #endregion #region internal constructor declaration internal PasswordAlphabet() { alphabetBuffer = new char[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; upperBound = alphabetBuffer.GetUpperBound(0); } #endregion #region public indexer declarations public char this[int index] { get { return alphabetBuffer[index % upperBound]; } } public char this[byte bIndex] { get { return this[(int)bIndex]; } } #endregion } #endregionVB.NET#Region "internal type declarations" Friend Class PasswordAlphabet #Region "private member declarations" Private Shared _current As PasswordAlphabet Private Shared synObject As New Object Private alphabetBuffer As Char() Private upperBound As Integer = 0 #End Region #Region "Public Properties" Public Shared ReadOnly Property Current() As PasswordAlphabet Get If _current Is Nothing Then SyncLock (synObject) If _current Is Nothing Then _current = New PasswordAlphabet End If End SyncLock End If Return _current End Get End Property #End Region #Region "Internal Constructor declaration" Friend Sub PasswordAlphabet() Dim anArray As Char() = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} alphabetBuffer = anArray upperBound = UBound(alphabetBuffer) End Sub #End Region #Region "Public Indexer Declarations" Default Public ReadOnly Property Item(ByVal index As Integer) As Char Get Return alphabetBuffer(index Mod upperBound) End Get End Property #End Region End Class #End RegionC++.NETref class PasswordAlphabet { private: static PasswordAlphabet^ _current; static Object synObject; array^ alphabetBuffer; int upperBound ; public: static property PasswordAlphabet^ Current { public: PasswordAlphabet^ get() { if (_current ==nullptr) { System::Threading::Mutex m; m.WaitOne(); if (_current==nullptr) _current = gcnew PasswordAlphabet(); } return _current; } } private: PasswordAlphabet() { alphabetBuffer = gcnew array{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; upperBound = alphabetBuffer->GetUpperBound(0); } public: inline char operator[](int index) { return alphabetBuffer[ index % upperBound ]; } inline char operator[](Byte bIndex) { return operator[]( (int)bIndex ); } };Dikkat ederseniz bu sınıf da singleton design pattern i gerçeklenmiştir. Aynı işiyapan iki ayrı indexer tanımlanmıştır. Uygulamamızın ilerleyen aşamalarında ikiindexer"a da ihtiyacımız olabilir. Burada dikkat edilmesi gereken array"in indeksinindışında bir indisdeki kayıt istendiğinde istenen indis için arrayin boyutu üzerindenmod alınmaktadır.Şimdi PasswordGenerator sınıfımıza yeniden odaklanalım.Öncelikle verilen byte dizisiiçin tanımladığımız alfabe üzerinde şifreyi oluşturan public metodumuzu tanımlayalım:C#public string GetPassword(byte[] buffer) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < buffer.Length; i++) sb.Append(PasswordAlphabet.Current[buffer[i]]); return sb.ToString(); }VB.NETPublic Function GetPassword(ByVal buffer As Byte()) As String Dim sb As New StringBuilder Dim i as integer For i = 0 To UBound(buffer) sb.Append(PasswordAlphabet.Current.Item(buffer(i))) Next Return sb.ToString() End FunctionC++.NETpublic: String^ GetPassword(array^ buffer) { StringBuilder sb; for (int i=0;iLength; i++) { interior_ptr>Byte> p = &buffer[i]; sb.Append((Char) PasswordAlphabet::Current->operator[]( *p ) ); } return sb.ToString(); }Şimdi de kullanılmayacak olan fakat algoritmanın gerektirdiği şu an ki kullanıcınınşifresini belirleyecek olan GenerateInitialPassword private metodunu yazalım:C#private string GenerateInitialPassword() { byte[] buffer = new byte[PasswordLength]; rngProvider.GetNonZeroBytes(buffer); return GetPassword(buffer); }VB.NETPrivate Function GenerateInitialPassword() As String Dim buffer(PasswordLength) As Byte rngProvider.GetNonZeroBytes(buffer) Return GetPassword(buffer) End FunctionC++.NETString^ GenerateInitialPassword() { array^ buffer = gcnew array(PasswordLength); rngProvider.GetNonZeroBytes(buffer); return GetPassword(buffer); }İstediğimiz sayıda ardışık şifreleri oluşturacak olan public metotumuzu geliştirelim.C#public string[] GeneratePasswordSeries(int pNumberOfPasswords) { #region local variable declarations string password = GenerateInitialPassword(); ArrayList alPassword = new ArrayList(); #endregion #region Generate Passwords for (int i = 0; i < pNumberOfPasswords; i++) { password = GetPassword( hashAlgorithm.ComputeHash( Encoding.Default.GetBytes(password))); alPassword.Add(password); } alPassword.Reverse(); #endregion return (string[])alPassword.ToArray(typeof(string)); }VB.NETPublic Function GeneratePasswordSeries( _ ByVal pNumberOfPasswords) As String() Dim password As String = GenerateInitialPassword() Dim alPassword As New ArrayList Dim i As Integer For i = 0 To pNumberOfPasswords - 1 password = GetPassword( hashAlgorithm.ComputeHash( Encoding.Default.GetBytes(password))) alPassword.Add(password) Next alPassword.Reverse() Return alPassword.ToArray() End FunctionC++.NETarray^ GeneratePasswordSeries(int pNumberOfPasswords) { String^ password = GenerateInitialPassword(); ArrayList alPassword; for (int i=0; i>pNumberOfPasswords; i++) { password= GetPassword( hashAlgorithm.ComputeHash( Encoding::Default->GetBytes(password))); alPassword.Add(password); } alPassword.Reverse(); return reinterpret_cast^>( alPassword.ToArray( System::String::typeid ) ); }Geliştirdiğimiz bu sınıf ile ardışık 30 şifre oluşturun. Kullanıcının girdiği şifreyiSHA1Managed sınıfının hashAlgorithm isminde bir örneğini oluşturup, test edebilirsiniz.PasswordGenerator.Current.GetPassword( hashAlgorithm.ComputeHash( Encoding.Default.GetBytes(pwd) ) )Buradaki pwd kullanıcının uygulama giriş için kullandığı şifreyi ifade eder. Eğerki bu fonksiyon ile oluşturulan şifre bir önceki şifreye eşit ise kullanıcımız doğrulanmışdemektir. Kayıtlı şifreyi yeni girilen şifre ile değiştirerek sisteme giriş işleminitamamlamış oluyoruz....Önemli Not: Bu makaledeki kodlar VS.NET 2005 üzerinde çalışacak şekilde yazılmıştır. C++.NET te yazılan kodlar da sadece VS.NET 2005 ile çalışacaktır. Son Söz: Bu uygulamayı geliştirerek çok daha güvenli bir yapı kurabilirsiniz ki bence olmasıgereken bu sınıfa bir web servisi aracılığıyla erişilmesidir.Bir sonraki makalemizdegörüşünceye kadar güvende kalın... İlgili Makaleler: Şifre SeçimiTek Kullanımlık Şifre Referanslar: RFC1938, A One-Time Password SystemYazar : Yunus Emre ALPÖZENe-Posta : yunus.alpozen et msakademik.net

Döküman Arama

Başlık :

Kapat