Web Q&A

Secure Passwords, Nested XML, and More

Edited by Nancy Michell

Code download available at: WebQA0311.exe (114 KB)
Browse the Code Online

Q I need to create a regular expression to validate an entry in a form and make sure that a value conforms to strong password guidelines. How can I do this?

Q I need to create a regular expression to validate an entry in a form and make sure that a value conforms to strong password guidelines. How can I do this?

A On the whole, regular expressions are good for matching particular patterns. In contrast, a good password should be devoid of recognizable patterns.

A On the whole, regular expressions are good for matching particular patterns. In contrast, a good password should be devoid of recognizable patterns.

Here is some Visual Basic® .NET code (the ideas can be easily converted to JScript®) that uses regular expressions to determine if a password contains certain characters (a through z, A through Z, 0 through 9, and so on); it then figures out if those matches mean that the password is strong (see Figure 1). The ValidatePassword function checks for strong password characteristics, such as:

  • At least minLength characters in the password
  • At least minNumUpper (uppercase) characters
  • At least minNumLower (lowercase) characters
  • At least minNumNumbers (numeric) characters
  • At least minNumSpecial (non-alphanumeric) characters
  • At least minNumCategories of the above five categories

Then it returns a string with information about which of the tests the password may have failed.

Figure 1 Searching for Characters

Function ValidatePasswordWork(ByVal pwd As String, _
    ByVal minLength As Integer, _
    ByVal minNumCategories As Integer, _
    ByVal minNumUpper As Integer, _
    ByVal minNumLower As Integer, _
    ByVal minNumNumbers As Integer, _
    ByVal minNumSpecial As Integer) As String

    Dim ret As String = ""
    Dim CrLf As String = ControlChars.CrLf
 
    ' May want to replace [A-Z] with \p{Lu}, to allow for 
    ' Unicode uppercase letters.
    Dim reUpper As New System.Text.RegularExpressions.Regex("[A-Z]")
    Dim reLower As New System.Text.RegularExpressions.Regex("[a-z]")
    Dim reNumber As New System.Text.RegularExpressions.Regex("[0-9]")
    ' Special is "none of the above"
    Dim reSpecial As New System.Text.RegularExpressions.Regex _
        ("[^a-zA-Z_0-9]")
 
    ' Check the length.
    If Len(pwd) < minLength Then _
        ret &= "Length less than " & minLength.ToString & CrLf
 
    ' Check for minimum number of occurrences.
    If reUpper.Matches(pwd).Count < minNumUpper Then _
        ret &= "Fewer than " & minNumUpper & " uppers" & CrLf
    If reLower.Matches(pwd).Count < minNumLower Then _
        ret &= "Fewer than " & minNumLower & " lowers" & CrLf
    If reNumber.Matches(pwd).Count < minNumNumbers Then _
        ret &= "Fewer than " & minNumNumbers & " digits" & CrLf
    If reSpecial.Matches(pwd).Count < minNumSpecial Then _
        ret &= "Fewer than " & minNumSpecial & " special" & _
            CrLf
 
    ' Check for the right number of categories.
    Dim numCategories As Integer = 0
 
    If reUpper.Match(pwd).Success Then numCategories += 1
    If reLower.Match(pwd).Success Then numCategories += 1
    If reNumber.Match(pwd).Success Then numCategories += 1
    If reSpecial.Match(pwd).Success Then numCategories += 1
 
    If numCategories < minNumCategories Then _
      ret &= "Fewer than " & minNumCategories & " categories" _
          & CrLf
 
    ' Passes all the checks, so it is OK.
    Return ret
End Function
 
Function ValidatePassword(ByVal pwd As String, _
    Optional ByVal minLength As Integer = 7, _
    Optional ByVal minNumCategories As Integer = 3, _
    Optional ByVal minNumUpper As Integer = 1, _
    Optional ByVal minNumLower As Integer = 1, _
    Optional ByVal minNumNumbers As Integer = 1, _
    Optional ByVal minNumSpecial As Integer = 1) _
As Boolean
    ' Returns True is the password passes all the tests.
    ' The default values may be overridden.
    Dim temp As String
    temp = ValidatePasswordWork(pwd, minLength, 
        minNumCategories, _
        minNumUpper, minNumLower, minNumNumbers, minNumSpecial)
    ' Uncomment the following line to see which tests the 
    ' password failed.
    '      MsgBox(temp)
 
    If temp = "" Then
        ' Passed all tests.
        Return True
    Else
        Return False
    End If
End Function

A regular expression that matches the kinds of patterns you'd want would be shorter than writing a human-readable, maintainable program, but that's about all. Good password characteristics tend to cover global features of the string like how many symbols there are, which can be hard to express in a regular language.

Another measure you might consider is the amount of entropy that is in a given password. Entropy is the way unpredictability is measured. For example, if you happen to know that the first six letters of a ten-letter password are "Securi" then, though it is entirely possible that my password is "Securi^*3g", it is considerably more likely that my password really is "Security!". Those first six letters are so low-entropy that you can very easily predict the rest. If you know that the first six letters of a ten-letter password are "&5&ddV" then there is very little you can do to predict the next four, short of trying all of them. That's much higher entropy.

Anyone who is going to try and brute-force crack passwords is going to start with the small number of low-entropy passwords first (combinations of English words, for example) before trying every possible combination of letters, numbers, and symbols. By measuring the entropy of a password you can come up with some idea about how strong it is. Of course, it is important to remember that most passwords are not compromised through cracking but rather through human factors—the "Yellow Sticky Attack" or social engineering attacks, for example.

Sometimes adding even a very small amount of entropy to a password can dramatically increase its resistance to attacks. For example, which password is harder to crack, ^ft3DGpT or ^ft3DGpTABCDEFGHIJKLMNOPQRSTUVWXYZ?

The second password has barely any more entropy than the first, but by simple virtue of its length, is nearly uncrackable. This is particularly true when you consider weak authentication mechanisms—high entropy and length is best, but in a pinch lots of length plus some entropy will do.

Writing Secure Code by Michael Howard and David C. LeBlanc (Microsoft Press®, 2002) has a few guidelines on computing the equivalent bit size of a given password but does not go deep into the mathematics of computing the entropy of a password. For more, see Checklist: Create Strong Passwords.

Q I wrote a tool in Visual Basic .NET that parses an XML schema and creates documentation for it. I'm using the XMLSchema objects in the System.XMLSchema namespace.

Q I wrote a tool in Visual Basic .NET that parses an XML schema and creates documentation for it. I'm using the XMLSchema objects in the System.XMLSchema namespace.

The schema is filled with complex types that have child elements. However, there are no corresponding elements to the types; they are just included in the schema. As a result, my code isn't picking them up. How do I get to the nested child elements of a complex type that doesn't correspond to an element?

The following is an excerpt from the schema:

<xsd:complexType name="TextCell_Type" mixed="true">
    <xsd:choice minOccurs="0" maxOccurs="unbounded">
        <xsd:element name="cp" type="cp_Type" minOccurs="0" 
            maxOccurs="unbounded" />
        <xsd:element name="pp" type="pp_Type" minOccurs="0" 
            maxOccurs="unbounded" />
        <xsd:element name="tp" type="tp_Type" minOccurs="0" 
            maxOccurs="unbounded" />
        <xsd:element name="fld" type="fld_Type" minOccurs="0" 
            maxOccurs="unbounded" />
    </xsd:choice>
</xsd:complexType>

A If you iterate over the SchemaTypes collection on the XmlSchema object you will get to all global complex types including those that are not used as the type of any element. To get at the nested child elements, you need to drill down to the ContentTypeParticle property (see Figure 2). This is the post-compiled property, so make sure the schema is compiled.

A If you iterate over the SchemaTypes collection on the XmlSchema object you will get to all global complex types including those that are not used as the type of any element. To get at the nested child elements, you need to drill down to the ContentTypeParticle property (see Figure 2). This is the post-compiled property, so make sure the schema is compiled.

Figure 2 Get Nested Children

private void PrintParticles(string url) {
    XmlSchema schema = XmlSchema.Read(new XmlTextReader(url, 
    new NameTable()), new ValidationEventHandler(ValidationCallback));
    schema.Compile(new ValidationEventHandler(ValidationCallback));
    foreach(XmlSchemaType type in schema.SchemaTypes.Values) {  
// This will go over all global types even those that are not 
// used as an element's type.
        XmlSchemaComplexType complexType = type as 
        XmlSchemaComplexType;
        if (complexType != null) {
            TraverseParticle(complexType.ContentTypeParticle, schema);
        }
    }

    foreach(XmlSchemaElement elem in schema.Elements) {
        XmlSchemaComplexType complexType = elem.ElementType as 
        XmlSchemaComplexType;
    if (complexType != null && complexType.QualifiedName == 
        XmlQualifiedName.Empty) { //Only local types of an 
                                  // element since already 
                                  // traversed global types
        TraverseParticle(complexType.ContentTypeParticle, schema);
        }
    }
} //End of PrintParticles

private void TraverseParticle(XmlSchemaParticle particle, 
    XmlSchema schema) {
    
    if (particle is XmlSchemaElement) {
        XmlSchemaElement elem = particle as XmlSchemaElement;
        Console.WriteLine("XmlSchemaElement: " + 
        elem.QualifiedName.ToString());
        }
else if (particle is XmlSchemaAny) {
    Console.WriteLine("Any");
}
else if (particle is XmlSchemaGroupBase) { 
        Console.WriteLine("Seq, All or Choice");
        XmlSchemaGroupBase baseParticle = particle as 
        XmlSchemaGroupBase;
        foreach(XmlSchemaParticle subParticle in 
        baseParticle.Items) {
            TraverseParticle(subParticle, schema);
        }
    }
}

Q I used the following code to query two specific pieces of information about network adapters in Windows® Management Instrumentation (WMI):

On Error Resume Next
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & _
    "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * from" & _ 
    "Win32_NetworkAdapterConfiguration",,48)
For Each objItem in colItems
    Wscript.Echo "Description: " & objItem.Description
    Wscript.Echo "SettingID: " & objItem.SettingID
Next
 

How could I modify this code to query remotely? I want to query info about machine A from machine B.

Q I used the following code to query two specific pieces of information about network adapters in Windows® Management Instrumentation (WMI):

On Error Resume Next
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & _
    "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * from" & _ 
    "Win32_NetworkAdapterConfiguration",,48)
For Each objItem in colItems
    Wscript.Echo "Description: " & objItem.Description
    Wscript.Echo "SettingID: " & objItem.SettingID
Next
 

How could I modify this code to query remotely? I want to query info about machine A from machine B.

A Here is a little script to get all of the network interface card (NIC) information and dump it to a servername.xml file. It takes in a list of servers from a text file.

A Here is a little script to get all of the network interface card (NIC) information and dump it to a servername.xml file. It takes in a list of servers from a text file.

Figure 4 Enter Server Name

Figure 4** Enter Server Name **

The function in Figure 3 gets the NIC details. Figure 4 shows the code in action. For the complete code, see the download at the link at the top of this article.

Figure 3 Get NIC Details

Function GetNICDetails(ServerName)
   On Error Resume Next
   Dim WshShell
   Dim NetworkAdapterSet
   Dim NetworkAdapter
   Dim DataHolder

   Set NetworkAdapterSet = _
      GetObject("winmgmts:{impersonationLevel=impersonate}//" & _
      ServerName).ExecQuery("SELECT PNPDeviceID, Index FROM" & _ 
      Win32_NetworkAdapter WHERE Manufacturer != 'Microsoft'")

   Select Case Err.Number
      Case(0)
         If IsNull (NetworkAdapterSet) OR IsEmpty 
            (NetworkAdapterSet) Then
            'WMI unable to query NIC
            DataErrors = DataErrors & vbCrLf & _
               "<server name=" & """" & ServerName & """" _
               & ">" & vbCrLf & vbTab & _
               "<errnumber>" & NetAdaptersNotFound & _
               "</errnumber>" & vbCrLf & _
               "<errdescription>The server " & ServerName" & _
               " does not have any Network Adapters which" & _
               " can be queried. " & _
               "Please check that the adapters are " & _
               " recognized by the OS and try" & _
               " again.</errdescription>" & vbCrLf & "</server>"
            'Log the fact we couldn't get any adapter info
            LogDetails "There are no network adapters that" & _
                       "can be queried for this Server." & _
                       vbCrLf & "Server Name: " & ServerName

         Else
            ' Undefine the connection NIC index.
            ' Loop through the list of adapters looking for the 
            ' adapter details
            For Each NetworkAdapter In NetworkAdapterSet
               If (Not IsNull(NetworkAdapter) and 
                   (Len(NetworkAdapter.PnPDeviceID) > 0)) Then
                  'Call the sub routine to get the registry values for us
                 DataHolder = DataHolder & vbCrLf & _
                 GetRegValues(CStr(NetworkAdapter.PnPDeviceID), _
                 ServerName)
               End If
            Next
         End If

         DataHolder = "<nics>" & vbCrLf & vbTab & _
             DataHolder & "</nics>"

      Case (ServerNotFound)
         DataErrors = DataErrors & vbCrLf & _
            "<server name=" & """" & ServerName & """" & ">" & _
            vbCrLf & vbTab & _
            "<errnumber>" & ServerNotFound & "</errnumber>" & _
            vbCrLf "<errdescription>The server " & _
            ServerName & " could not be found. Check that " & _
            " the server has " & _
            "been switched and\or connected to the" & _
            "network</errdescription>" & vbCrLf & _
            "</server>"
      Case Else
         DataErrors = DataErrors & vbCrLf & _
            "<server name=" & """" & ServerName & """" & _
            ">" & vbCrLf & vbTab & _
            "<errnumber>" & Err.Number & "</errnumber>" & _
            vbCrLf & _
            "<errdescription>An error has occurred.  The" & _
            " description for the error is " & _
            Err.Description & "</errdescription>" & vbCrLf & _
            "</server>"
   End Select

   If Err.Number <> 0 Then
        LogDetails "Error occurred in 'GetNICDetails'" & _
                   "function. Error details are:" & _
                    vbCrLf & "Error Number: " & Err.Number & _
                    vbCrLf & "Error Details: " & _
                    Err.Description & _
                    vbCrLf & "Server Name: " & ServerName
      Err.Clear
      DataHolder = ""
    End If

   Set NetworkAdapterSet = Nothing
   Set NetworkAdapter = Nothing

   GetNICDetails = DataHolder
End Function

Q Say you have a date time string of "2003-07-02 00:06:32.700" and you know it is Greenwich Mean Time. Is there an easy way of converting this date time string to Pacific Standard Time?

Q Say you have a date time string of "2003-07-02 00:06:32.700" and you know it is Greenwich Mean Time. Is there an easy way of converting this date time string to Pacific Standard Time?

A You need to convert your date into a Common Information Model (CIM) datetime format. This would then convert it into whatever time the local machine is running:

set datetime = 
    CreateObject("WbemScripting.SWbemDateTime")
 
datetime.Value = "20030313004910.000000+000"
wscript.echo datetime.GetVarDate(false)

A You need to convert your date into a Common Information Model (CIM) datetime format. This would then convert it into whatever time the local machine is running:

set datetime = 
    CreateObject("WbemScripting.SWbemDateTime")
 
datetime.Value = "20030313004910.000000+000"
wscript.echo datetime.GetVarDate(false)

Q How can I get the name of the system drive using JScript?

Q How can I get the name of the system drive using JScript?

A From Windows Script Host (WSH), you can do the following:

var shell = new ActiveXObject("WScript.Shell");
WScript.Echo("The System Drive is " + 
shell.ExpandEnvironmentStrings("%SystemDrive%"));

That will work if that's the only property you want to discover about the system drive. Here's an alternate solution that solves the more general problem of querying all info about the system drive:

var SystemFolder = 1;
var FSO = new   
ActiveXObject("Scripting.FileSystemObject");
var Folder = FSO.GetSpecialFolder(SystemFolder);
var drive = Folder.Drive.DriveLetter;

Now you can extract all the info you want about the drive—its name, path, date created—everything.

A From Windows Script Host (WSH), you can do the following:

var shell = new ActiveXObject("WScript.Shell");
WScript.Echo("The System Drive is " + 
shell.ExpandEnvironmentStrings("%SystemDrive%"));

That will work if that's the only property you want to discover about the system drive. Here's an alternate solution that solves the more general problem of querying all info about the system drive:

var SystemFolder = 1;
var FSO = new   
ActiveXObject("Scripting.FileSystemObject");
var Folder = FSO.GetSpecialFolder(SystemFolder);
var drive = Folder.Drive.DriveLetter;

Now you can extract all the info you want about the drive—its name, path, date created—everything.

Got a question? Send questions and comments to  webqa@microsoft.com.

Thanks to the following Microsoft developers: Rhett Attwood, Chris Beatie, Jason Cooke, Dan Grass, Rob Hawthorne, Priya Lakshminarayanan, Eric Lippert, Srikanth Mandadi, Pat Miller, Dare Obasanjo, Michael Sharps, Michael Whalen, Lisa Wollin, Mike Wyvel.