title: OpenXML Protect Spreadsheet Workbook with Password
body: <p>I am trying to create a protected spreadsheet document with OpenXML SDK. However, the <code>WorkbookHashValue</code> generated is not correct and thus the workbook cannot be unprotected.</p>
  
  <pre><code>var password = Encoding.UTF8.GetBytes("123");
  var salt = new byte[16];
  new RNGCryptoServiceProvider().GetNonZeroBytes(salt);
  var spinCount = 100000U;
  
  using (var document = SpreadsheetDocument.Create("text.xlsx", SpreadsheetDocumentType.Workbook))
  {
      var workbookPart = document.AddWorkbookPart();
      var workbook = new Workbook();
      WorkbookProtection workbookProtection = new WorkbookProtection()
      {
          LockStructure = true,
          WorkbookAlgorithmName = "SHA-512",
          WorkbookHashValue = Convert.ToBase64String(GetPasswordHash(password, salt, spinCount)),
          WorkbookSaltValue = Convert.ToBase64String(salt),
          WorkbookSpinCount = spinCount
      };
      var sheets = new Sheets();
      var sheet = new Sheet
      {
          Name = "Sheet 1",
          SheetId = 1U,
          Id = "rId1"
      };
      sheets.Append(sheet);
      workbook.Append(workbookProtection);
      workbook.Append(sheets);
      workbookPart.Workbook = workbook;
  
      var worksheetPart = workbookPart.AddNewPart&lt;WorksheetPart&gt;("rId1");
      var worksheet = new Worksheet();
      var sheetData = new SheetData();
      worksheet.Append(sheetData);
      worksheetPart.Worksheet = worksheet;
  }
  </code></pre>
  
  <pre><code>private byte[] GetPasswordHash(byte[] password, byte[] salt, uint spinCount)
  {
      using (var sha512 = SHA512.Create())
      {
          var buffer = new byte[salt.Length + password.Length];
          Array.Copy(salt, buffer, salt.Length);
          Array.Copy(password, 0, buffer, salt.Length, password.Length);
          byte[] hash = sha512.ComputeHash(buffer);
          buffer = new byte[hash.Length + 4];
          for (var i = 0U; i &lt; spinCount; i++)
          {
              Array.Copy(hash, buffer, hash.Length);
              Array.Copy(BitConverter.GetBytes(i), 0, buffer, hash.Length, 4);
              hash = sha512.ComputeHash(buffer);
          }
          return hash;
      }
  }
  </code></pre>
  
  <p>The correct hash for password <code>123</code> with salt <code>VAQd0dyl7U67APquHio1lQ==</code> should be <code>2ZwXmW83qax0iUfzSkbhwAOVSDHAm6S/v9irWWTzdoFDgzO2Kc82P3Z9BAwbWqFLzN4rKaL0APOMzQ5tA7TBDw==</code>, but the above code generated <code>z5ebojaXN/sD4ps9yurRCpSTDp+kSuTz+HN2PyKmGuicNgszAPKxfsE+kTgOEbGhT/VqSbwTd++oyAJxJh0L3A==</code>.</p>
  
  <p>I tried comparing the source code of Apache POI and didn't found any mistakes</p>
  
  <pre><code>public static byte[] hashPassword(String password, HashAlgorithm hashAlgorithm, byte[] salt, int spinCount, boolean iteratorFirst) {
      // If no password was given, use the default
      if (password == null) {
          password = Decryptor.DEFAULT_PASSWORD;
      }
  
      MessageDigest hashAlg = getMessageDigest(hashAlgorithm);
  
      hashAlg.update(salt);
      byte[] hash = hashAlg.digest(StringUtil.getToUnicodeLE(password));
      byte[] iterator = new byte[LittleEndianConsts.INT_SIZE];
  
      byte[] first = (iteratorFirst ? iterator : hash);
      byte[] second = (iteratorFirst ? hash : iterator);
  
      try {
          for (int i = 0; i &lt; spinCount; i++) {
              LittleEndian.putInt(iterator, 0, i);
              hashAlg.reset();
              hashAlg.update(first);
              hashAlg.update(second);
              hashAlg.digest(hash, 0, hash.length); // don't create hash buffer everytime new
          }
      } catch (DigestException e) {
          throw new EncryptedDocumentException("error in password hashing");
      }
  
      return hash;
  }
  </code></pre>
  
  <p>(<code>iteratorFirst</code> is false for workbook protection)</p>