The sample also depends on a hashing helper that signs and validates the header values.
That code is not in the MSI - but the sample code is instead shown below. You can create a project that outputs a DLL called "HashingHelper.dll". For it to be callable from Classic ASP, you will need to run "gacutil -i" to install the assembly in the GAC and
you will also need to use "regasm" to register the class in the assembly with COM.
HashHelper.cs: using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
namespace HashingHelper
{
public class HashHelper
{
private static string hashKey =
"You need to supply a valid 128 character long hex key here";
public static byte[] ConvertStringKeyToByteArray(string stringizedKeyValue)
{
byte[] keyBuffer = new byte[64];
for (int i = 0; i < hashKey.Length; i = i + 2)
{
keyBuffer[i / 2] = Byte.Parse(hashKey.Substring(i, 2), System.Globalization.NumberStyles.HexNumber);
}
return keyBuffer;
}
public static string ConvertByteArrayToString(byte[] value)
{
StringBuilder sb = new StringBuilder(128);
foreach (byte b in value)
{
int i = (int)b;
sb.Append(i.ToString("X2"));
}
return sb.ToString();
}
public static string HashStringValue(string valueToHash)
{
using (HMACSHA1 hms = new HMACSHA1(bKey))
{
return ConvertByteArrayToString(hms.ComputeHash(Encoding.Unicode.GetBytes(valueToHash)));
}
}
public static bool ValidateHash(string value, string hash)
{
using (HMACSHA1 hms = new HMACSHA1(bKey))
{
if (HashStringValue(value) != hash)
return false;
else
return true;
}
}
using System.Reflection;
using System.Security;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("HashingHelper")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Me")]
[assembly: AssemblyProduct("HashingHelper")]
[assembly: AssemblyCopyright("Copyright Me")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM componenets. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(true)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("09f8b521-ad1b-4545-94e6-51e94d89a2ac")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AllowPartiallyTrustedCallers()]
-Stefan
----------------------------------------------------------
This posting is provided "AS IS" with no warranties, and confers no rights.
Were you able to get this solution working? I've built the app and have it configured to authenticate against AD. Works great for anything running behind the aspnet_isapi filter, but this will not protect Classic ASP files.
Has anyone actually tried to use the above solution and got it to work protecting the ASP files?
There are two general classes of encryption: one-way encryption and two-way encryption. Two-way encryption is the most common form of encryption. It takes a plain-text input and encrypts it into some encrypted text. Then, at some later point in time, this encrypted
text can be decrypted, which results in the plain-text that was originally encrypted. Two-way encryption is useful for private communications. For example, imagine that you wanted to send an eCommerce Web site your credit card number to make a purchase. You
wouldn't want to have your credit card numbers sent over the Internet in plain-text, because someone monitoring the Internet might see your credit card information whiz by. Rather, you'd want to send your credit card information as an encrypted message. When
this encrypted message was received by the Web server, it could be decrypted, resulting in the actual credit card numbers.
One-way encryption, on the other hand, only allows for a plain-text input to be encrypted. That is, there is no way to decrypt the data. At first it may seem that such an encryption scheme is not needed - after all, why would you only want to be able to
encrypt data and not decrypt it? A practical example of this is storing encrypted passwords on a database server, which is what this article is all about! That is, when a user creates a new account, he or she will supply their password. Rather than storing
this password to the database as plain text, this password can be encrypted using a one-way encrypting algorithm and its encrypted form can be saved to the database. That way, if someone gains access to the database they will not see any of the passwords in
plain-text.
MD5 encryption is an example of a one-way encryption algorithm; specifically, MD5 encryption maps a plain-text string of an arbitrary length to a small encrypted string of a fixed length. Two important properties of the MD5 algorithm are that given an encrypted
output it is impossible to revert back to the initial, plain-text input, and that any given input always maps to the same encrypted value. The former property means that even if a hacker sees the encrypted output of the MD5 algorithm, they can't "unwind it"
and get back at the plain-text input; the latter property means that if you wish to encrypt a particular plain-text input that it will always result in the same encrypted output.
The
MD5CyptoServiceProvider
class in the
System.Security.Cryptography
namespace
of the .NET Framework provides a class for performing one-way, MD5 encryption. It is this class that we'll
use to provide encrypted passwords in our database. Before we examine how to implement encrypted passwords,
let's take a minute to investigate the functionality of the
MD5CyptoServiceProvider
class.
The main method of this class is the
ComputeHash
method, which takes as input an array
of bytes (the plain-text string to encrypt) and returns an array of bytes, which is the encrypted value.
Commonly we'll want to encrypt a string, meaning that we must convert our string to an array of bytes in
order to use the
ComputeHash
method. This conversion can be accomplished by using the
UTF8Encoding
encoding class, as shown in the following example:
'The string we wish to encrypt Dim strPlainText as String = "Encrypt me!"
'The array of bytes that will contain the encrypted value of strPlainText Dim hashedDataBytes as Byte()
'The encoder class used to convert strPlainText to an array of bytes Dim encoder as New UTF8Encoding()
'Create an instance of the MD5CryptoServiceProvider class Dim md5Hasher as New MD5CryptoServiceProvider()
'Call ComputeHash, passing in the plain-text string as an array of bytes 'The return value is the encrypted value, as an array of bytes hashedDataBytes = md5Hasher.ComputeHash(encoder.GetBytes(strPlainText))
method deals with arrays of bytes, not strings. Hence,
to encrypt a plain-text string you must convert it to an array of bytes. This is accomplished by using
the
UTF8Encoding
encoding class's
GetBytes
method (see the last line of
the above code example). The return result of the
ComputeHash
method is the encrypted data as an array of bytes. (For all practical purposes, the encrypted array has exactly 16 elements.)
Now that we've discussed the motivation behind using encrypted passwords and looked at the MD5 encryption algorithm, let's turn our attention to actually implementing encrypted passwords using MD5..... contd...
Earlier I mentioned that most sites that support user accounts do so by using a database table named something like
UserAccount
, with fields like
UserName
and
Password
. In the case where the password is saved as plain-text, both the
UserName
and
Password
fields are of type
varchar
. However, if we plan on using encrypted
passwords we need to change the type of the
Password
field from a
varchar
to
a
binary
type of length 16. This change is needed because the encrypted version of the
user's password will be a 16-element array of bytes.
Now, whenever a user creates an account, we need to be certain to store the encrypted form of the password
they selected into the database. The following ASP.NET code provides a sample Web page for creating a
user account. It prompts the user for their username and password and stores these values into a
UserAccount
database table. However, instead of storing the password as-entered by the
user, it first encrypts it using the MD5 code we examined earlier, and then saves to the database this
encrypted version.
<%@ Import Namespace="System.Security.Cryptography" %> <%@ Import Namespace="System.Text" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <script runat="server" language="VB"> Sub CreateAccount(sender as Object, e as EventArgs) '1. Create a connection Const strConnString as String = "connection string" Dim objConn as New SqlConnection(strConnString)
'2. Create a command object for the query Dim strSQL as String = _ "INSERT INTO UserAccount(Username,Password) " & _ "VALUES(@Username, @Password)" Dim objCmd as New SqlCommand(strSQL, objConn)
'3. Create parameters Dim paramUsername as SqlParameter paramUsername = New SqlParameter("@Username", SqlDbType.VarChar, 25) paramUsername.Value = txtUsername.Text objCmd.Parameters.Add(paramUsername)
'Encrypt the password Dim md5Hasher as New MD5CryptoServiceProvider()
Dim hashedBytes as Byte() Dim encoder as New UTF8Encoding()
Dim paramPwd as SqlParameter paramPwd = New SqlParameter("@Password", SqlDbType.Binary, 16) paramPwd.Value = hashedBytes objCmd.Parameters.Add(paramPwd)
'Insert the records into the database objConn.Open() objCmd.ExecuteNonQuery() objConn.Close()
'Redirect user to confirmation page... End Sub </script>
). The code provides the user with
two TextBoxes, one for the username and one for the password. Once the user has supplied these values
and clicked the "Create Account" button, the
CreateAccount
event handler will be executed,
and the database table
UserAccounts
will have a new row added representing the new user.
The screenshot to the right shows the values in the
UserAccounts
table after some users
have been created. Note that the password contains a 16-element binary array, representing the encrypted
password. Clearly if someone were to be able to examine the
UserAccounts
table they could
not deduce the plain-text password of any of the users.
Using MD5 To Authenticate a User
Since we are storing the passwords in encrypted form, and since, by the nature of a one-way encryption
algorithm it is impossible to retrace from the encrypted form to the plain-text form, you may be wondering
how in the world we'll authenticate a user. That is, when a user wants to login and supplies her username
and password, how will we know if she provided the correct password?
Recall one of the properties of MD5 - that for any plain-text input the encrypted version of the input
will be the same, always. That is, if we use MD5 to generate an encrypted form of the plain-text string
"my password", the encrypted version of this will be the same today and forever more. Therefore,
to authenticate a user we can simply take the password they provide, encrypt it using MD5, and then see
if that encrypted form exists in the database (along with their username). The following code performs
this check, logging in a user:
<%@ Import Namespace="System.Security.Cryptography" %> <%@ Import Namespace="System.Text" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <script runat="server" language="VB"> Sub Login(sender as Object, e as EventArgs) '1. Create a connection Const strConnString as String = "connection string" Dim objConn as New SqlConnection(strConnString)
'2. Create a command object for the query Dim strSQL as String = "SELECT COUNT(*) FROM UserAccount " & _ "WHERE Username=@Username AND Password=@Password" Dim objCmd as New SqlCommand(strSQL, objConn)
'3. Create parameters Dim paramUsername as SqlParameter paramUsername = New SqlParameter("@Username", SqlDbType.VarChar, 25) paramUsername.Value = txtUsername.Text objCmd.Parameters.Add(paramUsername)
'Encrypt the password Dim md5Hasher as New MD5CryptoServiceProvider()
Dim hashedDataBytes as Byte() Dim encoder as New UTF8Encoding()
Dim paramPwd as SqlParameter paramPwd = New SqlParameter("@Password", SqlDbType.Binary, 16) paramPwd.Value = hashedDataBytes objCmd.Parameters.Add(paramPwd)
'Insert the records into the database objConn.Open() Dim iResults as Integer = objCmd.ExecuteScalar() objConn.Close()
If iResults = 1 then 'The user was found in the DB Else 'The user was not found in the DB End If End Sub </script>
Limitations of Storing Encrypted Passwords in the Database
Before you decide whether or not to employ encrypted passwords in your next project, there are a few
limitations to be aware of. First, realize that since the passwords are encrypted, there is no way
to determine what a user's password is! While this is exactly what we were after by encrypting passwords
in the first place, it means that you cannot provide users with a "Click here to have your password emailed
to you" feature. Rather, if the user forgets his password he'll have to have his password reset to some
random password, and then be emailed that new, random password. Essentially we cannot email the user his
forgotten password because there's no way to determine what, exactly, his password is!
Also, converting from a plain-text password system to an encrypted system is possible, but can be
a bit difficult. Essentially, you need to create a new table with the
Password
field being
of type
binary
and of length 16. Next, you have to use an ASP.NET Web page or a .NET
program to read the contents of the existing user database, and for each record, add it to the
new table making sure to encrypt the user's password using MD5. Be careful not to delete the old user's
account information until you're certain that you copied over their information correctly!
Important Security Note!
The hashing technique discussed in this article is susceptible to dictionary attacks. A much more secure approach
is to salt the hash in some manner. For a thorough discussion on what salting is and why it's an important
precaution, be sure to read: Could you Pass the Salt? Improving the Security in
Encrypting Passwords using MD5.
Conclusion
In this article we looked at how to use MD5 to provide encrypted passwords in a database. The .NET
Framework contains an
MD5CryptoServiceProvider
class that provides MD5 encryption functionality. Recall that MD5 is a one-way encrypting algorithm, meaning that it can be used to encrypt
a plain-text string to an encrypted form, but not back the other way around. Encrypted passwords make sense for applications where one can do much damage by discovering a user's password, but due to their limitations, may not be suitable for less sensitive
applications.
It is recommend using other algorithms instead, such as
SHA.
Zhao Ji Ma
Sincerely,
Microsoft Online Community Support
“Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread. ”
sschack
Contributor
3085 Points
617 Posts
Microsoft
Download info for using ASP.NET 2.0 authentication with a Classic ASP site
Jun 20, 2005 08:09 PM|LINK
The sample ASP.NET/ASP site with the custom Http handler is available for download at:
http://download.microsoft.com/download/9/C/D/9CD0A246-5F89-4854-B4B6-CAE0D3F386BE/WEB343.msi
The sample also depends on a hashing helper that signs and validates the header values.
That code is not in the MSI - but the sample code is instead shown below. You can create a project that outputs a DLL called "HashingHelper.dll". For it to be callable from Classic ASP, you will need to run "gacutil -i" to install the assembly in the GAC and you will also need to use "regasm" to register the class in the assembly with COM.
HashHelper.cs:
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
namespace HashingHelper
{
public class HashHelper
{
private static string hashKey =
"You need to supply a valid 128 character long hex key here";
private static byte[] bKey;
static HashHelper()
{
bKey = ConvertStringKeyToByteArray(hashKey);
}
public static byte[] ConvertStringKeyToByteArray(string stringizedKeyValue)
{
byte[] keyBuffer = new byte[64];
for (int i = 0; i < hashKey.Length; i = i + 2)
{
keyBuffer[i / 2] = Byte.Parse(hashKey.Substring(i, 2), System.Globalization.NumberStyles.HexNumber);
}
return keyBuffer;
}
public static string ConvertByteArrayToString(byte[] value)
{
StringBuilder sb = new StringBuilder(128);
foreach (byte b in value)
{
int i = (int)b;
sb.Append(i.ToString("X2"));
}
return sb.ToString();
}
public static string HashStringValue(string valueToHash)
{
using (HMACSHA1 hms = new HMACSHA1(bKey))
{
return ConvertByteArrayToString(hms.ComputeHash(Encoding.Unicode.GetBytes(valueToHash)));
}
}
public static bool ValidateHash(string value, string hash)
{
using (HMACSHA1 hms = new HMACSHA1(bKey))
{
if (HashStringValue(value) != hash)
return false;
else
return true;
}
}
#region COM support
public HashHelper() { }
public bool ValidateHashCOM(string value, string hash)
{
return HashHelper.ValidateHash(value, hash);
}
#endregion
}
}
AssemblyInfo.cs:
using System.Reflection;
using System.Security;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("HashingHelper")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Me")]
[assembly: AssemblyProduct("HashingHelper")]
[assembly: AssemblyCopyright("Copyright Me")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM componenets. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(true)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("09f8b521-ad1b-4545-94e6-51e94d89a2ac")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AllowPartiallyTrustedCallers()]
----------------------------------------------------------
This posting is provided "AS IS" with no warranties, and confers no rights.
XpEndIf
0 Points
0 Posts
Can I use the Algorithm of myself?
Apr 05, 2006 11:40 AM|LINK
Hi!
Can I use the Algorithm of mine?
for example
using md5 and Other algorithms when ENCRYPTING the password?
just an idea! but i don;t know whether it can be used?
{EndIf;}
lucasstark
Member
123 Points
42 Posts
Re: Can I use the Algorithm of myself?
May 26, 2006 03:16 PM|LINK
Were you able to get this solution working? I've built the app and have it configured to authenticate against AD. Works great for anything running behind the aspnet_isapi filter, but this will not protect Classic ASP files.
Has anyone actually tried to use the above solution and got it to work protecting the ASP files?
weknowthewor...
Member
224 Points
56 Posts
Re: Can I use the Algorithm of myself?
Dec 11, 2006 11:47 AM|LINK
One-way encryption, on the other hand, only allows for a plain-text input to be encrypted. That is, there is no way to decrypt the data. At first it may seem that such an encryption scheme is not needed - after all, why would you only want to be able to encrypt data and not decrypt it? A practical example of this is storing encrypted passwords on a database server, which is what this article is all about! That is, when a user creates a new account, he or she will supply their password. Rather than storing this password to the database as plain text, this password can be encrypted using a one-way encrypting algorithm and its encrypted form can be saved to the database. That way, if someone gains access to the database they will not see any of the passwords in plain-text.
MD5 encryption is an example of a one-way encryption algorithm; specifically, MD5 encryption maps a plain-text string of an arbitrary length to a small encrypted string of a fixed length. Two important properties of the MD5 algorithm are that given an encrypted output it is impossible to revert back to the initial, plain-text input, and that any given input always maps to the same encrypted value. The former property means that even if a hacker sees the encrypted output of the MD5 algorithm, they can't "unwind it" and get back at the plain-text input; the latter property means that if you wish to encrypt a particular plain-text input that it will always result in the same encrypted output.
The
class in the namespace of the .NET Framework provides a class for performing one-way, MD5 encryption. It is this class that we'll use to provide encrypted passwords in our database. Before we examine how to implement encrypted passwords, let's take a minute to investigate the functionality of the class. The main method of this class is the method, which takes as input an array of bytes (the plain-text string to encrypt) and returns an array of bytes, which is the encrypted value. Commonly we'll want to encrypt a string, meaning that we must convert our string to an array of bytes in order to use the method. This conversion can be accomplished by using the encoding class, as shown in the following example:Keep in mind that the
method deals with arrays of bytes, not strings. Hence, to encrypt a plain-text string you must convert it to an array of bytes. This is accomplished by using the encoding class's method (see the last line of the above code example). The return result of the method is the encrypted data as an array of bytes. (For all practical purposes, the encrypted array has exactly 16 elements.) Now that we've discussed the motivation behind using encrypted passwords and looked at the MD5 encryption algorithm, let's turn our attention to actually implementing encrypted passwords using MD5..... contd...weknowthewor...
Member
224 Points
56 Posts
Re: Can I use the Algorithm of myself?
Dec 11, 2006 12:02 PM|LINK
Now, whenever a user creates an account, we need to be certain to store the encrypted form of the password they selected into the database. The following ASP.NET code provides a sample Web page for creating a user account. It prompts the user for their username and password and stores these values into a
database table. However, instead of storing the password as-entered by the user, it first encrypts it using the MD5 code we examined earlier, and then saves to the database this encrypted version.
Note that the above code sample imports a number of namespaces. These namespaces are imported to save
typing (for example, if the
namespace were not imported, when referring to the class, the code would have to appear as: ). The code provides the user with two TextBoxes, one for the username and one for the password. Once the user has supplied these values and clicked the "Create Account" button, the event handler will be executed, and the database table will have a new row added representing the new user.The screenshot to the right shows the values in the
table after some users have been created. Note that the password contains a 16-element binary array, representing the encrypted password. Clearly if someone were to be able to examine the table they could not deduce the plain-text password of any of the users.Using MD5 To Authenticate a User
Since we are storing the passwords in encrypted form, and since, by the nature of a one-way encryption algorithm it is impossible to retrace from the encrypted form to the plain-text form, you may be wondering how in the world we'll authenticate a user. That is, when a user wants to login and supplies her username and password, how will we know if she provided the correct password?
Recall one of the properties of MD5 - that for any plain-text input the encrypted version of the input will be the same, always. That is, if we use MD5 to generate an encrypted form of the plain-text string "my password", the encrypted version of this will be the same today and forever more. Therefore, to authenticate a user we can simply take the password they provide, encrypt it using MD5, and then see if that encrypted form exists in the database (along with their username). The following code performs this check, logging in a user:
Limitations of Storing Encrypted Passwords in the Database
Before you decide whether or not to employ encrypted passwords in your next project, there are a few limitations to be aware of. First, realize that since the passwords are encrypted, there is no way to determine what a user's password is! While this is exactly what we were after by encrypting passwords in the first place, it means that you cannot provide users with a "Click here to have your password emailed to you" feature. Rather, if the user forgets his password he'll have to have his password reset to some random password, and then be emailed that new, random password. Essentially we cannot email the user his forgotten password because there's no way to determine what, exactly, his password is!
Also, converting from a plain-text password system to an encrypted system is possible, but can be a bit difficult. Essentially, you need to create a new table with the
field being of type and of length 16. Next, you have to use an ASP.NET Web page or a .NET program to read the contents of the existing user database, and for each record, add it to the new table making sure to encrypt the user's password using MD5. Be careful not to delete the old user's account information until you're certain that you copied over their information correctly!Conclusion
class that provides MD5 encryption functionality. Recall that MD5 is a one-way encrypting algorithm, meaning that it can be used to encrypt a plain-text string to an encrypted form, but not back the other way around. Encrypted passwords make sense for applications where one can do much damage by discovering a user's password, but due to their limitations, may not be suitable for less sensitive applications.In this article we looked at how to use MD5 to provide encrypted passwords in a database. The .NET Framework contains an
Happy Programming!
Darksat
Member
2 Points
3 Posts
Re: Can I use the Algorithm of myself?
Feb 23, 2007 11:22 AM|LINK
Not sure if thats a good idea.
I saw this post about weaknesses in the MD5 algo.
http://www.greghughes.net/rant/CommentView,guid,bae8e927-bc0e-4af1-a373-e8f0a92c34dd.aspx
You should really use a block cypher like Triple Des instead.
MD5 Triple Des
Zhao Ji Ma -...
All-Star
23104 Points
2380 Posts
Re: Can I use the Algorithm of myself?
Apr 11, 2007 10:02 AM|LINK
Hi Darksat,
It is recommend using other algorithms instead, such as SHA.
Sincerely,
Microsoft Online Community Support
“Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread. ”
GeorgeZ
Contributor
2325 Points
552 Posts
Re: Can I use the Algorithm of myself?
Jul 04, 2007 09:56 AM|LINK
Just see a person is asking: http://channel9.msdn.com/ShowPost.aspx?PostID=166168
So can I call membership provider the similar way using Assembly or COM wrapper.
best regs.
sim_sai
Member
2 Points
5 Posts
Re: Can I use the Algorithm of myself?
Dec 17, 2007 04:45 AM|LINK
Can I use the Algorithm of mine?
for example
using md5 and Other algorithms when ENCRYPTING the password?
just an idea! but i don;t know whether it can be used[:O]
lesonfu
Member
12 Points
6 Posts
Re: Can I use the Algorithm of myself?
Mar 21, 2008 03:29 AM|LINK
I want to form login with 3 level, can you help me? By C#.