| /* |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * ==================================================================== |
| */ |
| |
| package org.apache.poi.xssf.usermodel.helpers; |
| |
| import java.security.SecureRandom; |
| import java.util.Arrays; |
| import java.util.Locale; |
| |
| import javax.xml.bind.DatatypeConverter; |
| import javax.xml.namespace.QName; |
| |
| import org.apache.poi.poifs.crypt.CryptoFunctions; |
| import org.apache.poi.poifs.crypt.HashAlgorithm; |
| import org.apache.poi.util.Internal; |
| import org.apache.xmlbeans.XmlCursor; |
| import org.apache.xmlbeans.XmlObject; |
| |
| @Internal(since="3.15 beta 3") |
| public final class XSSFPasswordHelper { |
| private XSSFPasswordHelper() { |
| // no instances of this static class |
| } |
| |
| /** |
| * Sets the XORed or hashed password |
| * |
| * @param xobj the xmlbeans object which contains the password attributes |
| * @param password the password, if null, the password attributes will be removed |
| * @param hashAlgo the hash algorithm, if null the password will be XORed |
| * @param prefix the prefix of the password attributes, may be null |
| */ |
| public static void setPassword(XmlObject xobj, String password, HashAlgorithm hashAlgo, String prefix) { |
| XmlCursor cur = xobj.newCursor(); |
| |
| if (password == null) { |
| cur.removeAttribute(getAttrName(prefix, "password")); |
| cur.removeAttribute(getAttrName(prefix, "algorithmName")); |
| cur.removeAttribute(getAttrName(prefix, "hashValue")); |
| cur.removeAttribute(getAttrName(prefix, "saltValue")); |
| cur.removeAttribute(getAttrName(prefix, "spinCount")); |
| return; |
| } |
| |
| cur.toFirstContentToken(); |
| if (hashAlgo == null) { |
| int hash = CryptoFunctions.createXorVerifier1(password); |
| cur.insertAttributeWithValue(getAttrName(prefix, "password"), |
| String.format(Locale.ROOT, "%04X", hash).toUpperCase(Locale.ROOT)); |
| } else { |
| SecureRandom random = new SecureRandom(); |
| byte salt[] = random.generateSeed(16); |
| |
| // Iterations specifies the number of times the hashing function shall be iteratively run (using each |
| // iteration's result as the input for the next iteration). |
| int spinCount = 100000; |
| |
| // Implementation Notes List: |
| // --> In this third stage, the reversed byte order legacy hash from the second stage shall |
| // be converted to Unicode hex string representation |
| byte hash[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCount, false); |
| |
| cur.insertAttributeWithValue(getAttrName(prefix, "algorithmName"), hashAlgo.jceId); |
| cur.insertAttributeWithValue(getAttrName(prefix, "hashValue"), DatatypeConverter.printBase64Binary(hash)); |
| cur.insertAttributeWithValue(getAttrName(prefix, "saltValue"), DatatypeConverter.printBase64Binary(salt)); |
| cur.insertAttributeWithValue(getAttrName(prefix, "spinCount"), ""+spinCount); |
| } |
| cur.dispose(); |
| } |
| |
| /** |
| * Validates the password, i.e. |
| * calculates the hash of the given password and compares it against the stored hash |
| * |
| * @param xobj the xmlbeans object which contains the password attributes |
| * @param password the password, if null the method will always return false, |
| * even if there's no password set |
| * @param prefix the prefix of the password attributes, may be null |
| * |
| * @return true, if the hashes match |
| */ |
| public static boolean validatePassword(XmlObject xobj, String password, String prefix) { |
| // TODO: is "velvetSweatshop" the default password? |
| if (password == null) return false; |
| |
| XmlCursor cur = xobj.newCursor(); |
| String xorHashVal = cur.getAttributeText(getAttrName(prefix, "password")); |
| String algoName = cur.getAttributeText(getAttrName(prefix, "algorithmName")); |
| String hashVal = cur.getAttributeText(getAttrName(prefix, "hashValue")); |
| String saltVal = cur.getAttributeText(getAttrName(prefix, "saltValue")); |
| String spinCount = cur.getAttributeText(getAttrName(prefix, "spinCount")); |
| cur.dispose(); |
| |
| if (xorHashVal != null) { |
| int hash1 = Integer.parseInt(xorHashVal, 16); |
| int hash2 = CryptoFunctions.createXorVerifier1(password); |
| return hash1 == hash2; |
| } else { |
| if (hashVal == null || algoName == null || saltVal == null || spinCount == null) { |
| return false; |
| } |
| |
| byte hash1[] = DatatypeConverter.parseBase64Binary(hashVal); |
| HashAlgorithm hashAlgo = HashAlgorithm.fromString(algoName); |
| byte salt[] = DatatypeConverter.parseBase64Binary(saltVal); |
| int spinCnt = Integer.parseInt(spinCount); |
| byte hash2[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCnt, false); |
| return Arrays.equals(hash1, hash2); |
| } |
| } |
| |
| |
| private static QName getAttrName(String prefix, String name) { |
| if (prefix == null || "".equals(prefix)) { |
| return new QName(name); |
| } else { |
| return new QName(prefix+Character.toUpperCase(name.charAt(0))+name.substring(1)); |
| } |
| } |
| } |