I am calling a stored procedure to create and hash a new password. It is being called from a .NET/C# WebAPI controller.
_sqlDataContext.ExecuteCommand("[Users].[SetNewPassword]", new List<SqlParameter>
{
new SqlParameter("#UserId", SqlDbType.UniqueIdentifier) { Value = user.Id },
new SqlParameter("#Password", SqlDbType.NVarChar, 256) { Value = command.Password }
});
userId and command.Password are a GUID and simple string respectively.
The stored procedure being called hashes and stores the password as follows:
ALTER PROC [Users].[SetNewPassword]
#UserId uniqueidentifier,
#Password NVARCHAR(256) -- Unsalted and unhashed
AS
BEGIN TRANSACTION [Transaction1]
BEGIN TRY
DECLARE #PasswordSalt uniqueidentifier = NEWID();
DECLARE #SaltedPassword binary(64);
SET #SaltedPassword = HASHBYTES('SHA2_512', #Password + CAST(#PasswordSalt as NVARCHAR(256)));
-- Insert new password
INSERT INTO Passwords (UserId, Password, PasswordSalt, UpdatedDate)
VALUES (#UserId, #SaltedPassword, #PasswordSalt, GETDATE());
COMMIT TRANSACTION [Transaction1]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION [Transaction1]
PRINT ERROR_MESSAGE()
END CATCH
When I call this from the WebAPI / C# method, I get a different hash than when I execute the stored procedure directly in SQL Server Management Studio.
Furthermore, if I execute the SetPassword stored procedure from C#, the following stored procedure - ValidatePassword - to validate a password fails.
This is because the hash stored in the database does not match the hash being generated by ValidatePassword
ALTER PROC [Users].[ValidatePassword]
#UserId uniqueidentifier,
#Password NVARCHAR(256)
AS
BEGIN
DECLARE #PasswordHash binary(64) = HASHBYTES('SHA2_512', (SELECT #Password + CAST((SELECT p.PasswordSalt FROM Passwords p WHERE p.UserId = #UserId) AS NVARCHAR(256))))
SELECT
CASE
WHEN EXISTS (SELECT UserId
FROM Passwords
WHERE UserId = #UserId
AND Password = #PasswordHash)
THEN 1
ELSE 0
END
SELECT #passwordHash;
END
Question / problem:
Why is the hash generated by the stored procedure when called from C# different to the one generated from within SQL Server Management Studio? Is there is an issue with unicode/binary conversion or something like that?
Please note I appreciate that 'rolling your own' authorisation is not always a good idea, but this is addressing a temporary issue with a legacy app.
Related
I have the following Stored procedure
ALTER PROCEDURE dbo.USER_AUTH
(
#username varchar,
#password varchar
)
AS
BEGIN
SELECT * FROM users
WHERE username = #username
AND password = #password;
END
And there are Two rows in the users table.
This gives an empty reader error
_Command = new SqlCommand("[dbo].[USER_AUTH]", _Connection);
_Command.CommandType = System.Data.CommandType.StoredProcedure;
_Command.Parameters.AddWithValue("#username", _Username);
_Command.Parameters.AddWithValue("#password", _Password);
reader = _Command.ExecuteReader();
While this works fine
_Command = new SqlCommand("select * from users where
username=#username and password=#password" , _Connection);
_Command.Parameters.AddWithValue("#username", _Username);
_Command.Parameters.AddWithValue("#password", _Password);
reader = _Command.ExecuteReader();
Where _Connection is SqlConnection _Connection , _Command is SqlCommand _Command and
reader is SqlDataReader
This happens because your stored procedure declares
ALTER PROCEDURE dbo.USER_AUTH
(
#username varchar,
#password varchar
)
without specifying a size for the two varchars. In this way just one char is passed to the parameters and of course nothing is retrieved
Change the sp to
ALTER PROCEDURE dbo.USER_AUTH
(
#username nvarchar(30),
#password nvarchar(30)
)
or whatever size are your two database fields
You have to specify the length for varchar
So if we don't specify the length ourself these are the default values SQL Server uses - which means the data what we would be expecting to get stored in the database would have got silently truncated without our knowledge.
Bad habits to kick : declaring VARCHAR without (length)
ALTER PROCEDURE dbo.USER_AUTH
(
#username varchar(20),
#password varchar(20)
)
AS
BEGIN
SELECT * FROM users
WHERE username = #username
AND password = #password;
END
I have created a stored procedure for SQL Server 2014.
There are two parameters: Name which is a user name and Hash which is password md5 hash. I check in the database if the md5 hashes are equal (first hash is from the program and the second one is already stored in the database).
If I just run a query (not a stored procedure) in the database (or in program using commandType.Text) - everything works and the user is being selected, but when I run the exact thing but using stored procedures, the SqlReader in C# has no elements returned, which most likely means that the conditions during those variable comparison were not met.
Maybe I am doing something wrong?
I also have about 10 other stored procedures for reading or/and writing to the database, everything works except this one.
Here is the procedure:
CREATE PROCEDURE GetHash
#Name nvarchar(50),
#Hash nvarchar(200)
AS
BEGIN
SET NOCOUNT ON;
SELECT Orders.orderId, Employee.name, Employee.surname
FROM Orders
LEFT JOIN Employee ON Orders.orderId = Employee.id
WHERE batchName = '#Name' AND productCode = '#Hash'
END
GO
Code part:
public Boolean VerifyPassword(string name, string password)
{
var paramsList = new List<SqlParameter> { new SqlParameter("#Name", name), new SqlParameter("#Hash", GetMd5Hash(password)) };
const string ProcedureName = "GetHash";
var ActiveUser = new DBController().GetFromDatabase(ProcedureName, "Login", "EJL15_DB", paramsList).ToList();
return ActiveUser.Count > 0;
}
And from Database Controller
private void SetCommandProperties(string procedureName, IEnumerable<SqlParameter> paramsList)
{
this.sqlCommand.CommandText = procedureName;
this.sqlCommand.CommandType = CommandType.StoredProcedure;
foreach (var curParam in paramsList)
this.sqlCommand.Parameters.Add(curParam);
this.sqlCommand.CommandTimeout = 15;
}
You don't need to quote the parameters in the stored procedure. Do this instead:
CREATE PROCEDURE GetHash
#Name nvarchar(50),
#Hash nvarchar(200)
AS
BEGIN
SET NOCOUNT ON;
SELECT Orders.orderId,
Employee.name,
Employee.surname
FROM Orders
LEFT JOIN Employee
ON Orders.orderId=Employee.id
WHERE batchName = #Name
AND productCode = #Hash
END
I just wonder, obviously your #Hash parameter passed to the stored
procedure is a user's password. But for some reason your WHERE clause
in the procedure goes like that:
"WHERE batchName='#Name' AND productCode='#Hash'"
Is there a chance your condition is incorrect? I guess it should be something like: Employee.password = #Hash
You should not put '' around your variables. Otherwise your comparison is totally wrong.
I am creating a web page to store data which is read from a Microsoft Excel worksheet.
I am passing the data to a stored procedure in SQL Server 2008.
Here is my C# code:
SqlConnection conn = new SqlConnection(AppDB);
conn.Open();
SqlCommand command = new SqlCommand();
command.Connection = conn;
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "sp_ins_TaskDetails_from_Excel";
command.Parameters.AddWithValue("#TaskDetails", dtTaskDetailsFromExcel);
string sReturnValue = command.ExecuteNonQuery().ToString();
The stored procedure has one user defined table data type as parameter.
Here is my stored procedure:
ALTER PROCEDURE dbo.sp_ins_TaskDetails_from_Excel
(
#TaskDetails TypeInsertTaskFromExcel11 readonly
)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE
#ProjectID int,
#ProjectTeamID int,
#TeamLeaderUserID int,
#TaskCategoryName varchar(max),
#TaskDescription varchar(max),
#TeamMemberUserID int,
#TaskPriorityName varchar(10),
#PlanDuration float,
#PlanStartDate datetime,
#PlanEndDate datetime,
#TaskTypeName varchar(30),
#TaskStatusName varchar(30),
#TaskAllotName varchar(10),
#CreatedBy varchar(30),
#CreatedDate datetime,
#ISMailSend bit,
#Isvisible bit,
#UniqueID int
DECLARE TMSTaskDetailFromExcelCursor CURSOR FOR SELECT
ProjectID,
ProjectTeamID,
TeamLeaderUserID,
TaskCategoryName,
TaskDescription,
TeamMemberUserID,
TaskPriorityName,
PlanDuration,
PlanStartDate,
PlanEndDate,
TaskTypeName,
TaskStatusName,
TaskAllotName,
CreatedBy,
CreatedDate,
ISMailSend,
Isvisible,
UniqueID
FROM #TaskDetails
OPEN TMSTaskDetailFromExcelCursor
FETCH NEXT FROM TMSTaskDetailFromExcelCursor INTO
#ProjectID,
#ProjectTeamID,
#TeamLeaderUserID,
#TaskCategoryName,
#TaskDescription,
#TeamMemberUserID,
#TaskPriorityName,
#PlanDuration,
#PlanStartDate,
#PlanEndDate,
#TaskTypeName,
#TaskStatusName,
#TaskAllotName,
#CreatedBy,
#CreatedDate,
#ISMailSend,
#Isvisible,
#UniqueID;
WHILE ##FETCH_STATUS=0 BEGIN
-- Insert statements for procedure here
INSERT INTO ManageTasks (ProjectID, ProjectTeamID, TeamLeaderUserID,
TaskCategoryName,TaskDescription, TeamMemberUserID, TaskPriorityName,
PlanDuration, PlanStartDate, PlanEndDate, TaskTypeName,
TaskStatusName, TaskAllotName, CreatedBy, CreatedDate, ISMailSend, Isvisible,UniqueID)
VALUES (#ProjectID, #ProjectTeamID, #TeamLeaderUserID, #TaskCategoryName,
#TaskDescription, #TeamMemberUserID, #TaskPriorityName, #PlanDuration,
#PlanStartDate, #PlanEndDate, #TaskTypeName, #TaskStatusName,
#TaskAllotName, #CreatedBy, #CreatedDate, #ISMailSend,#Isvisible,#UniqueID);
FETCH NEXT FROM TMSTaskDetailFromExcelCursor INTO
#ProjectID,
#ProjectTeamID,
#TeamLeaderUserID,
#TaskCategoryName,
#TaskDescription,
#TeamMemberUserID,
#TaskPriorityName,
#PlanDuration,
#PlanStartDate,
#PlanEndDate,
#TaskTypeName,
#TaskStatusName,
#TaskAllotName,
#CreatedBy,
#CreatedDate,
#ISMailSend,
#Isvisible,
#UniqueID;
END;
CLOSE TMSTaskDetailFromExcelCursor;
DEALLOCATE TMSTaskDetailFromExcelCursor;
END
It receives the data and using a cursor in the same stored procedure each row will be inserted using an Insert statement in the same procedure.
While executing, all the data from Excel is passed to the stored procedure as exactly mentioned in the user defined table type.
But, the values are not stored in database and the stored procedure returns -1.
All the values are in correct format and order. I don't know what is going wrong.
Is there something I should change in the stored procedures?
I have a strongly-typed dataset that is using DBDirectMethods to insert data into a database using calls to stored procedures. The stored procedures return the primary key of the newly-created record. Here's a sample stored procedure:
CREATE PROCEDURE dbo.CreateUser
(
#UserName VARCHAR(50)
#Password VARCHAR(50)
)
AS
SET NOCOUNT OFF
DECLARE #UserID INT
BEGIN TRANSACTION
INSERT INTO dbo.Users (UserName, Password) VALUES (#UserName, #Password)
SET #UserID = SCOPE_IDENTITY()
INSERT INTO dbo.Users_History (UserID, Status, TimeStamp) VALUES (#UserID, 'C', GETUTCDATE())
COMMIT TRANSACTION
RETURN #UserID
GO
If I execute the stored procedure from SSMS, then the user account is created, the history table updated, and the primary key returned. If I run my application using the strongly-typed dataset and have SQL Profiler ticking away, I can see the same code being executed; however, the dataset returns -1 as the primary key and breaks the app.
Inside the VS-generated code for the table adapter, the relevant lines are:
this._adapter.InsertCommand.CommandText = "dbo.CreateUser";
this._adapter.InsertCommand.CommandType = global::System.Data.CommandType.StoredProcedure;
this._adapter.InsertCommand.Parameters.Add(new global::System.Data.SqlClient.SqlParameter("#RETURN_VALUE", global::System.Data.SqlDbType.Int, 4, global::System.Data.ParameterDirection.ReturnValue, 10, 0, null, global::System.Data.DataRowVersion.Current, false, null, "", "", ""));
this._adapter.InsertCommand.Parameters.Add(new global::System.Data.SqlClient.SqlParameter("#UserName", global::System.Data.SqlDbType.VarChar, 50, global::System.Data.ParameterDirection.Input, 0, 0, "UserName", global::System.Data.DataRowVersion.Current, false, null, "", "", ""));
this._adapter.InsertCommand.Parameters.Add(new global::System.Data.SqlClient.SqlParameter("#Password", global::System.Data.SqlDbType.VarChar, 50, global::System.Data.ParameterDirection.Input, 0, 0, "Password", global::System.Data.DataRowVersion.Current, false, null, "", "", ""));
and
try {
int returnValue = this.Adapter.InsertCommand.ExecuteNonQuery();
return returnValue;
}
finally {
if ((previousConnectionState == global::System.Data.ConnectionState.Closed)) {
this.Adapter.InsertCommand.Connection.Close();
}
}
which is all just the standard boiler-plate code that VS usually generates - nothing has been edited by hand. It just doesn't pick up the return value.
I am running Windows 7 with SQL 2008 R2 SP1 Express.
From memory, the issue here is that the value ExecuteNonQuery() returns is the number of rows affected by the query.
Instead, your return value should be accessible via:
InsertCommand.Parameters["#RETURN_VALUE"].Value;
or
InsertCommand.Parameters[0].Value;
In addition, you can try changing your return parameter name to #UserID rather than #RETURN_VALUE but it should still work as you have it.
It seems that rather than returning only the PK, I need to return the entire new record. By changing my stored procedure to the following:
CREATE PROCEDURE dbo.CreateUser
(
#UserName VARCHAR(50)
#Password VARCHAR(50)
)
AS
SET NOCOUNT OFF
DECLARE #UserID INT
BEGIN TRANSACTION
INSERT INTO dbo.Users (UserName, Password) VALUES (#UserName, #Password)
SET #UserID = SCOPE_IDENTITY()
INSERT INTO dbo.Users_History (UserID, Status, TimeStamp) VALUES (#UserID, 'C', GETUTCDATE())
COMMIT TRANSACTION
SELECT UserID, UserName, Password FROM dbo.Users WHERE UserID = #UserID
GO
then ADO.NET automatically loads the returned result into the dataset, and I can get UserID from there.
You need to change the query type from ExecuteNonQuery to ExecuteScalar
See http://blogs.msdn.com/b/smartclientdata/archive/2005/10/31/returnidentityvaluequery.aspx
I've created a stored procedure that takes parameters to create a user. If the user already exists it sets the output parameter to 'User already exists' and does nothing more.
Now I've mapped this function (InsertNewUser) to my Entity Framework and am calling it like so:
context.InsertNewUser(email, name, passwordhash, salt, ???)
The ??? is where I'm having trouble. In the stored procedure this parameter is an OUTPUT parameter. I tried declaring a string and then passing in "out declaredString" but that wasn't correct.
I'm not sure I'm going about this the right way, any thoughts?
This is the Stored Procedure:
ALTER PROCEDURE dbo.InsertNewUser
(
#eMail nvarchar(256),
#firstName nvarchar(256),
#lastName nvarchar(256),
#passwordHash nvarchar(256),
#salt nvarchar(256),
#output nvarchar(256) OUTPUT
)
AS
/* Saves a user to the db. */
BEGIN
--First check if the user doesn't exist
IF EXISTS (SELECT eMail FROM UserSet WHERE eMail = #eMail)
--Return that user exists
SET #output = 'User exists'
ELSE
INSERT INTO UserSet
VALUES (#eMail, #firstName, #lastName, #passwordHash, #salt)
END
You can also write in the following way:
string output = "";
context.InsertNewUser(email, name, passwordhash, salt, ref output)
I solved it with this code:
//This will provide the parameter
System.Data.Objects.ObjectParameter parameter = new ObjectParameter("output", "nvarchar(256)");
//This will contain the returned values
ObjectResult result = context.CheckIfUserExists(eMail, parameter);
I solved it with following codeļ¼
//Execute stored procedure
ObjectParameter newkey = new ObjectParameter("newKey", typeof(char));
db.RF_GetNewKey("A", "B", "S",newkey);
//get new key output from stored procedure RF_GetNewKey
string myKey=newkey.Value.ToString();
With Entity Framework 6