Ever wanted to implement your own password policy?
This is how you can do it
This code remembers the last 10 passwords, and checks that a new password is not in this list.
The Web.Config:
<membership defaultProvider="BITMembershipProvider" userIsOnlineTimeWindow="15">
<providers>
<clear />
....
<add name="BITMembershipProvider" type="Glassdrive.provider.myOwnMembershipProvider" connectionStringName="SiteSqlServer" enablePasswordRetrieval="true" enablePasswordReset="true" requiresQuestionAndAnswer="false" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="0" requiresUniqueEmail="false" passwordFormat="Encrypted" applicationName="DotNetNuke" description="Stores and retrieves membership data from the local Microsoft SQL Server database" passwordStrengthRegularExpression="(?i)^(?!.*bitconsultancy).*$" />
....
The SQL Table:
/****** Object: Table [dbo].[gl_pw_hist] Script Date: 06/10/2009 10:30:00 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[gl_pw_hist](
[username] [nvarchar](100) NOT NULL,
[dt_cre] [datetime] NOT NULL CONSTRAINT [DF_gl_pw_hist_dt_cre] DEFAULT (getdate()),
[password] [nvarchar](255) NOT NULL,
CONSTRAINT [PK_gl_pw_hist] PRIMARY KEY CLUSTERED
(
[username] ASC,
[dt_cre] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
The Stored Procedure:
-- =============================================
-- Author: Olivier Jooris
-- Create date: 09/06/2009
-- Description: add a new password to the history table, and remove the last one
-- =============================================
create PROCEDURE [dbo].[gl_pw_histADD]
@username nvarchar(100), --username to remember history for
@password nvarchar(255), --encrypted password to remember
@NrOfPasswordsToRemember int = 0 --hostsetting for number of passwords to remember, if not entered, it will be selected from the table
AS
BEGIN
-- declare variables
declare @NrOfPasswordsToRemember_int int
declare @NrOfPasswordsToRemember_string nvarchar(50)
declare @NrOfPaswordsStored int
-- select the number of passwords to remember
IF @NrOfPasswordsToRemember > 0
BEGIN
set @NrOfPasswordsToRemember_int = @NrOfPasswordsToRemember
END
ELSE
BEGIN
-- select the setting from the table
SELECT @NrOfPasswordsToRemember_string = settingvalue from hostsettings where settingname = 'NrOfPasswordsToRemember'
-- try to convert the setting from text to integer
BEGIN TRY
set @NrOfPasswordsToRemember_int = cast(@NrOfPasswordsToRemember_string as int)
END TRY
BEGIN CATCH
set @NrOfPasswordsToRemember_int = 0
END CATCH;
END
-- insert the new pasword
INSERT INTO [gl_pw_hist] ([username],[password]) VALUES (@username, @password)
-- select the number of passwords in the system
select @NrOfPaswordsStored = count(*) from gl_pw_hist where username = @username
-- delete the last one if there are more than requested
if @NrOfPaswordsStored > @NrOfPasswordsToRemember_int
BEGIN
delete from gl_pw_hist
where username = @username
and dt_cre = (select min(dt_cre) from gl_pw_hist where username = @username)
END
END
The VB Code:
Imports System.Collections.Generic
Imports System.Data.SqlClient
Imports System.Security.Cryptography
Imports System.IO
Namespace BIT.provider
Public Class myOwnMembershipProvider
Inherits SqlMembershipProvider
Private my_salt As String = "tP+AyV0u7hdu+YOueFc7ow=="
#Region "Entry pointS"
Public Overrides Function ResetPassword(ByVal username As String, ByVal passwordAnswer As String) As String
Try
Return MyBase.ResetPassword(username, passwordAnswer)
Catch ex As Exception
Throw New Exception(ex.Message)
End Try
End Function
Public Overrides Function ChangePassword(ByVal username As String, ByVal oldPassword As String, ByVal newPassword As String) As Boolean
Try
'TODO:Does not work to inject a alert javascript into the calling page
'Dim i As Stream = DirectCast(HttpContext.Current.Response, System.Web.HttpResponse).OutputStream
'Dim smess As String = "<script language='javascript'>alert('this is an injection from my provider oljo');</script>"
'Dim bytes() As Byte = Encoding.ASCII.GetBytes(smess)
'i.Write(bytes, 0, bytes.Length)
'Dim executingPage As Page = TryCast(HttpContext.Current.Handler, Page)
'executingPage.RegisterStartupScript("kjhkjh", smess)
'executingPage.ClientScript.RegisterClientScriptBlock(executingPage.GetType, "oljokey", smess)
'executingPage.ClientScript.RegisterStartupScript(executingPage.GetType, "oljokey", smess)
'Dim c As ControlCollection = executingPage.Controls()
'DirectCast(DirectCast(c(2), System.Web.UI.Control), System.Web.UI.HtmlControls.HtmlGenericControl).Attributes.Add("oljokey", smess)
'HttpContext.Current.Response.Write(smess)
'executingPage.ClientScript.RegisterStartupScript(executingPage.GetType, "oljokey", smess)
'encode the password with my specific salt
Dim sEncodedPw As String = EncodePassword(newPassword, 2, my_salt)
'check if the password is already used
If check_new_pw_recurrence(username, sEncodedPw) Then
Return False
End If
'it was not used, continue with the standard code
Dim ok As Boolean = MyBase.ChangePassword(username, oldPassword, newPassword)
'if the change was ok, then add the new password to the history
If ok Then
add_new_password_to_the_list(username, sEncodedPw)
End If
'return the result from the standard code
Return ok
Catch ex As Exception
Throw New Exception(ex.Message)
End Try
End Function
#End Region
#Region "Private Functions"
''' <summary>
''' check if the password is recurring, the add new_history deletes older passwords
''' so the history contains always all passwords to check
''' </summary>
''' <param name="username"></param>
''' <param name="sEncPassword">encripted password</param>
''' <returns></returns>
''' <remarks></remarks>
Private Function check_new_pw_recurrence(ByVal username As String, ByVal sEncPassword As String) As Boolean
Try
'check it in the db
Return password_exists_in_db(username, sEncPassword)
Catch ex As Exception
Throw New Exception(ex.Message)
End Try
End Function
''' <summary>
''' encode the pasword with the salt
''' </summary>
''' <param name="pass"></param>
''' <param name="passwordFormat"></param>
''' <param name="salt"></param>
''' <returns></returns>
''' <remarks></remarks>
Private Function EncodePassword(ByVal pass As String, ByVal passwordFormat As Integer, ByVal salt As String) As String
If (passwordFormat = 0) Then
Return pass
End If
Dim bytes As Byte() = Encoding.Unicode.GetBytes(pass)
Dim src As Byte() = Convert.FromBase64String(salt)
Dim dst As Byte() = New Byte((src.Length + bytes.Length) - 1) {}
Dim inArray As Byte() = Nothing
Buffer.BlockCopy(src, 0, dst, 0, src.Length)
Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length)
If (passwordFormat = 1) Then
inArray = Me.EncryptPassword(bytes) 'not used for glassdrive, use un-salted encription
Else
inArray = Me.EncryptPassword(dst)
End If
Return Convert.ToBase64String(inArray)
End Function
#End Region
#Region "DB Access"
''' <summary>
''' read the gl_pw_hist table with the new password
''' </summary>
''' <param name="username"></param>
''' <param name="newPassword">encoded new password</param>
''' <returns></returns>
''' <remarks></remarks>
Private Function password_exists_in_db(ByVal username As String, ByVal newPassword As String) As Boolean
Dim ir As IDataReader = Nothing
Try
Dim s As String = "select * from [gl_pw_hist] where username = '@u' and password = '@p'"
s = s.Replace("@u", username).Replace("@p", newPassword)
ir = select_reader_sql(s)
Return ir.Read 'read it in the database
Catch ex As Exception
Throw New Exception(ex.Message)
Finally
If ir IsNot Nothing AndAlso Not ir.IsClosed Then
ir.Close()
End If
End Try
End Function
''' <summary>
''' adds the new password to the history, and removes older ones (depending on the hostsetting "NrOfPasswordsToRemember")
''' </summary>
''' <param name="username"></param>
''' <param name="newEncrPassword">new encrypted password</param>
''' <returns></returns>
''' <remarks></remarks>
Private Function add_new_password_to_the_list(ByVal username As String, ByVal newEncrPassword As String) As Boolean
Try
Dim p As New List(Of SqlParameter)
p.Add(New SqlParameter("@username", username))
p.Add(New SqlParameter("@password", newEncrPassword))
Dim i As Integer = exec_sqlsp("gl_pw_histADD", p.ToArray)
Catch ex As Exception
Throw New Exception(ex.Message)
End Try
End Function
#End Region
End Class
End Namespace