patternsqlMinor
Password generator function
Viewed 0 times
functiongeneratorpassword
Problem
What's the best way of creating a password generator user defined function function in SQL Server if it is not possible to use non deterministic funtions within functions?
I would like to create a function that returns a randomly generated varchar(10) using characters from a given string like this one:
"1234567890QWERTYUIOPASDFGHJKLZXCVBNM"
I have my own ideas that work but are not very natural solutions.
The idea behind the function is that if somebody requires a password reset I could do the following:
A function would be easier to deploy and reuse than a separate table of preallocated passwords.
I would like to create a function that returns a randomly generated varchar(10) using characters from a given string like this one:
"1234567890QWERTYUIOPASDFGHJKLZXCVBNM"
I have my own ideas that work but are not very natural solutions.
The idea behind the function is that if somebody requires a password reset I could do the following:
UPDATE Users SET Password = dbo.GeneratePassword() WHERE UserID = @UserIDA function would be easier to deploy and reuse than a separate table of preallocated passwords.
Solution
You have a few options (TL;DR -> working function is towards the end of Option #2, BUT also see Update section after Option #3 !):
-
If you are ok with just hex values (0 - 9 and A - F) you can just call CRYPT_GEN_RANDOM which was introduced in SQL Server 2008:
returns:
GO
CREATE FUNCTION dbo.GeneratePassword_InlineTVF(@RandomValue VARBINARY(10))
RETURNS TABLE
AS RETURN
SELECT CONVERT(VARCHAR(20), @RandomValue, 2) +
'~~' +
CONVERT(VARCHAR(20), @RandomValue, 2) AS [HowRandomAmI?];
GO
CREATE FUNCTION dbo.GeneratePassword_MultistatementTVF(@RandomValue VARBINARY(10))
RETURNS @Result TABLE ([HowRandomAmI?] VARCHAR(100))
AS
BEGIN
INSERT INTO @Result ([HowRandomAmI?])
VALUES (CONVERT(VARCHAR(20), @RandomValue, 2) +
'~~' +
CONVERT(VARCHAR(20), @RandomValue, 2));
RETURN;
END;
GO
CREATE FUNCTION dbo.GeneratePassword_ScalarUDF(@RandomValue VARBINARY(10))
RETURNS VARCHAR(100)
AS
BEGIN
RETURN CONVERT(VARCHAR(20), @RandomValue, 2) +
'~~' +
CONVERT(VARCHAR(20), @RandomValue, 2);
END;
GO
-- 27169EEC2B9CF7CC2731~~E9A95F49060BD41C0FF6
SELECT * FROM tempdb.dbo.GeneratePassword_MultistatementTVF(CRYPT_GEN_RANDOM(10));
-- 68EBC132814EA78602DE~~68EBC132814EA78602DE
SELECT tempdb.dbo.GeneratePassword_ScalarUDF(CRYPT_GEN_RANDOM(10)) AS [HowRandomAmI?];
-- 702DAF1C441C42FFBF5F~~702DAF1C441C42FFBF5F
FROM tempdb.dbo.GeneratePassword_InlineTVF(CRYPT_GEN_RANDOM(10)) rnd
CROSS JOIN [master].[sys].[objects];
---------------
1D57F0ABFDE44BCAED00~~AD57E9E2FF01768BB86F
264674BE1C9ABBC1572E~~6C75CD4D472935FDFA40
CB54CC6BB5F31F42FDEA~~B3EC7061027FCD36C9AC
44525355FC15655C1F4D~~4DDF874CD06BC4F2D7A4
0E51B6364F193F588C93~~08E2ED40ED9752267EF7
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN
WITH base(item) AS
(
SELECT NULL UNION ALL SELECT NULL UNION ALL SELECT NULL UNION ALL
SELECT NULL UNION ALL SELECT NULL UNION ALL SELECT NULL
), items(item) AS
(
SELECT NULL
FROM base b1
CROSS JOIN base b2
)
SELECT (
SELECT TOP (LEN(@RandomValue))
SUBSTRING('1234567890QWERTYUIOPASDFGHJKLZXCVBNM',
(CONVERT(TINYINT, SUBSTRING(@RandomValue, 1, 1)) % 36) + 1,
1) AS [text()]
FROM items
FOR XML PATH('')
) AS [RandomPassword];
SELECT * FROM tempdb.dbo.GeneratePassword_Real(CRYPT_GEN_RANDOM(@PasswordLength));
`
CAUTION: Please see Item # 2 below related to running this for multiple rows!
-
You can create a SQLCLR function that would have access to randomizing functionality. The main disadvantage here is that a Scalar function is much more likely to have its return value cached than an iTVF.
Update
-
Given the newly provided context for usage of this function from the Question update:
The idea behind the function is that if somebody requires a
-
If you are ok with just hex values (0 - 9 and A - F) you can just call CRYPT_GEN_RANDOM which was introduced in SQL Server 2008:
SELECT CRYPT_GEN_RANDOM(5) AS [Hex],
CONVERT(VARCHAR(20), CRYPT_GEN_RANDOM(5), 2) AS [HexStringWithout0x],
CONVERT(VARCHAR(20), CRYPT_GEN_RANDOM(10)) AS [Translated-ASCII],
CONVERT(NVARCHAR(20), CRYPT_GEN_RANDOM(20)) AS [Translated-UCS2orUTF16]
returns:
Hex HexStringWithout0x Translated-ASCII Translated-UCS2orUTF16
0x4F7D9ABBC4 0ECF378A7A ¿"bü
As you can see, you need to use the format option of 2 on the CONVERT function so that it doesn't give you characters that, while more diverse, might be harder to type in, and you don't get the leading 0x. But you can also see that even with a single row, multiple calls will get different return values (just like NEWID() ).
Please note the "length" values passed into the CRYPT_GEN_RANDOM function. Since a hex byte is 2 characters, you only need to generate a 5-byte value. The CONVERT(VARCHAR... uses a "length" of 10 since each hex byte becomes a character. And the CONVERT(NVARCHAR... uses a "length" of 20 since every 2 bytes will become a character (when running this in a database with a default collation that does not end in _SC, which is most cases).
-
You can always create your own algorithm and pass in the value of CRYPT_GEN_RANDOM for the random aspect. AND, if you can structure your algorithm to be a single SELECT such that it can be done in an Inline Table-Valued Function / iTVF (usually using CTEs), then you technically can use a non-deterministic function (at least effectively) since the function call will pass in the expression instead of the result of that expression, like it does with Multistatement TVFs and Scalar UDFs:
USE [tempdb];GO
CREATE FUNCTION dbo.GeneratePassword_InlineTVF(@RandomValue VARBINARY(10))
RETURNS TABLE
AS RETURN
SELECT CONVERT(VARCHAR(20), @RandomValue, 2) +
'~~' +
CONVERT(VARCHAR(20), @RandomValue, 2) AS [HowRandomAmI?];
GO
CREATE FUNCTION dbo.GeneratePassword_MultistatementTVF(@RandomValue VARBINARY(10))
RETURNS @Result TABLE ([HowRandomAmI?] VARCHAR(100))
AS
BEGIN
INSERT INTO @Result ([HowRandomAmI?])
VALUES (CONVERT(VARCHAR(20), @RandomValue, 2) +
'~~' +
CONVERT(VARCHAR(20), @RandomValue, 2));
RETURN;
END;
GO
CREATE FUNCTION dbo.GeneratePassword_ScalarUDF(@RandomValue VARBINARY(10))
RETURNS VARCHAR(100)
AS
BEGIN
RETURN CONVERT(VARCHAR(20), @RandomValue, 2) +
'~~' +
CONVERT(VARCHAR(20), @RandomValue, 2);
END;
GO
And then run the tests:
SELECT * FROM tempdb.dbo.GeneratePassword_InlineTVF(CRYPT_GEN_RANDOM(10));-- 27169EEC2B9CF7CC2731~~E9A95F49060BD41C0FF6
SELECT * FROM tempdb.dbo.GeneratePassword_MultistatementTVF(CRYPT_GEN_RANDOM(10));
-- 68EBC132814EA78602DE~~68EBC132814EA78602DE
SELECT tempdb.dbo.GeneratePassword_ScalarUDF(CRYPT_GEN_RANDOM(10)) AS [HowRandomAmI?];
-- 702DAF1C441C42FFBF5F~~702DAF1C441C42FFBF5F
And just to be sure about multiple rows in an iTVF:
SELECT TOP 5 rnd.[HowRandomAmI?]FROM tempdb.dbo.GeneratePassword_InlineTVF(CRYPT_GEN_RANDOM(10)) rnd
CROSS JOIN [master].[sys].[objects];
returns:
HowRandomAmI?---------------
1D57F0ABFDE44BCAED00~~AD57E9E2FF01768BB86F
264674BE1C9ABBC1572E~~6C75CD4D472935FDFA40
CB54CC6BB5F31F42FDEA~~B3EC7061027FCD36C9AC
44525355FC15655C1F4D~~4DDF874CD06BC4F2D7A4
0E51B6364F193F588C93~~08E2ED40ED9752267EF7
Putting all of this info into practice, we can do the following with regards to the original request:
CREATE FUNCTION dbo.GeneratePassword_Real(@RandomValue VARBINARY(30))RETURNS TABLE
WITH SCHEMABINDING
AS RETURN
WITH base(item) AS
(
SELECT NULL UNION ALL SELECT NULL UNION ALL SELECT NULL UNION ALL
SELECT NULL UNION ALL SELECT NULL UNION ALL SELECT NULL
), items(item) AS
(
SELECT NULL
FROM base b1
CROSS JOIN base b2
)
SELECT (
SELECT TOP (LEN(@RandomValue))
SUBSTRING('1234567890QWERTYUIOPASDFGHJKLZXCVBNM',
(CONVERT(TINYINT, SUBSTRING(@RandomValue, 1, 1)) % 36) + 1,
1) AS [text()]
FROM items
FOR XML PATH('')
) AS [RandomPassword];
And then execute it as follows:
DECLARE @PasswordLength INT = 10;SELECT * FROM tempdb.dbo.GeneratePassword_Real(CRYPT_GEN_RANDOM(@PasswordLength));
`
CAUTION: Please see Item # 2 below related to running this for multiple rows!
-
You can create a SQLCLR function that would have access to randomizing functionality. The main disadvantage here is that a Scalar function is much more likely to have its return value cached than an iTVF.
Update
-
Given the newly provided context for usage of this function from the Question update:
The idea behind the function is that if somebody requires a
Context
StackExchange Database Administrators Q#108439, answer score: 8
Revisions (0)
No revisions yet.