diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java index 2271e0b4d..5f1da12ec 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java @@ -18,8 +18,8 @@ package org.apache.poi.xssf.usermodel; import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; -import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.setPassword; -import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.validatePassword; +import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.setPassword; +import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.validatePassword; import java.io.IOException; import java.io.InputStream; diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index f3a5879db..afddcd9c4 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -18,8 +18,8 @@ package org.apache.poi.xssf.usermodel; import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; -import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.setPassword; -import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.validatePassword; +import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.setPassword; +import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.validatePassword; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPasswordHelper.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPasswordHelper.java new file mode 100644 index 000000000..46e47f688 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPasswordHelper.java @@ -0,0 +1,136 @@ +/* + * ==================================================================== + * 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)); + } + } +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java index 926058f89..4e3c90819 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java @@ -19,20 +19,17 @@ 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.poi.util.Removal; import org.apache.xmlbeans.XmlObject; +/** + * @deprecated POI 3.15 beta 3. Use {@link XSSFPasswordHelper} instead. + */ @Internal(since="3.15 beta 3") +@Deprecated +@Removal(version="3.17") public class XSSFPaswordHelper { /** * Sets the XORed or hashed password @@ -43,41 +40,7 @@ public class XSSFPaswordHelper { * @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(); + XSSFPasswordHelper.setPassword(xobj, password, hashAlgo, prefix); } /** @@ -92,41 +55,6 @@ public class XSSFPaswordHelper { * @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)); - } + return XSSFPasswordHelper.validatePassword(xobj, password, prefix); } }