/* * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, version 2.0, as published by the * Free Software Foundation. * * This program is also distributed with certain software (including but not * limited to OpenSSL) that is licensed under separate terms, as designated in a * particular file or component or in included license documentation. The * authors of MySQL hereby grant you an additional permission to link the * program and your derivative works with the separately licensed software that * they have included with MySQL. * * Without limiting anything contained in the foregoing, this file, which is * part of MySQL Connector/J, is also subject to the Universal FOSS Exception, * version 1.0, a copy of which can be found at * http://oss.oracle.com/licenses/universal-foss-exception. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, * for more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.mysql.cj.protocol; import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import com.mysql.cj.exceptions.AssertionFailedException; import com.mysql.cj.util.StringUtils; /** * Methods for doing secure authentication with MySQL-4.1 and newer. */ public class Security { private static int CACHING_SHA2_DIGEST_LENGTH = 32; /** * Encrypt/Decrypt function used for password encryption in authentication * * Simple XOR is used here but it is OK as we encrypt random strings * * @param from * IN Data for encryption * @param to * OUT Encrypt data to the buffer (may be the same) * @param scramble * IN Scramble used for encryption * @param length * IN Length of data to encrypt */ public static void xorString(byte[] from, byte[] to, byte[] scramble, int length) { int pos = 0; int scrambleLength = scramble.length; while (pos < length) { to[pos] = (byte) (from[pos] ^ scramble[pos % scrambleLength]); pos++; } } public static byte[] scramble411(String password, byte[] seed, String passwordEncoding) { byte[] passwordBytes = (passwordEncoding == null || passwordEncoding.length() == 0) ? StringUtils.getBytes(password) : StringUtils.getBytes(password, passwordEncoding); return scramble411(passwordBytes, seed); } /** * Hashing for MySQL-4.1 authentication. Algorithm is as follows (c.f. sql/auth/password.c): * *
     * SERVER: public_seed=create_random_string()
     * send(public_seed)
     *
     * CLIENT: recv(public_seed)
     * hash_stage1=sha1("password")
     * hash_stage2=sha1(hash_stage1)
     * reply=xor(hash_stage1, sha1(public_seed,hash_stage2))
     * send(reply)
     * 
*/ public static byte[] scramble411(byte[] password, byte[] seed) { MessageDigest md; try { md = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException ex) { throw new AssertionFailedException(ex); } byte[] passwordHashStage1 = md.digest(password); md.reset(); byte[] passwordHashStage2 = md.digest(passwordHashStage1); md.reset(); md.update(seed); md.update(passwordHashStage2); byte[] toBeXord = md.digest(); int numToXor = toBeXord.length; for (int i = 0; i < numToXor; i++) { toBeXord[i] = (byte) (toBeXord[i] ^ passwordHashStage1[i]); } return toBeXord; } /** * Scrambling for caching_sha2_password plugin. * *
     * Scramble = XOR(SHA2(password), SHA2(SHA2(SHA2(password)), Nonce))
     * 
* * @throws DigestException */ public static byte[] scrambleCachingSha2(byte[] password, byte[] seed) throws DigestException { /* * Server does it in 4 steps (see sql/auth/sha2_password_common.cc Generate_scramble::scramble method): * * SHA2(src) => digest_stage1 * SHA2(digest_stage1) => digest_stage2 * SHA2(digest_stage2, m_rnd) => scramble_stage1 * XOR(digest_stage1, scramble_stage1) => scramble */ MessageDigest md; try { md = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException ex) { throw new AssertionFailedException(ex); } byte[] dig1 = new byte[CACHING_SHA2_DIGEST_LENGTH]; byte[] dig2 = new byte[CACHING_SHA2_DIGEST_LENGTH]; byte[] scramble1 = new byte[CACHING_SHA2_DIGEST_LENGTH]; // SHA2(src) => digest_stage1 md.update(password, 0, password.length); md.digest(dig1, 0, CACHING_SHA2_DIGEST_LENGTH); md.reset(); // SHA2(digest_stage1) => digest_stage2 md.update(dig1, 0, dig1.length); md.digest(dig2, 0, CACHING_SHA2_DIGEST_LENGTH); md.reset(); // SHA2(digest_stage2, m_rnd) => scramble_stage1 md.update(dig2, 0, dig1.length); md.update(seed, 0, seed.length); md.digest(scramble1, 0, CACHING_SHA2_DIGEST_LENGTH); // XOR(digest_stage1, scramble_stage1) => scramble byte[] mysqlScrambleBuff = new byte[CACHING_SHA2_DIGEST_LENGTH]; xorString(dig1, mysqlScrambleBuff, scramble1, CACHING_SHA2_DIGEST_LENGTH); return mysqlScrambleBuff; } /** * Prevent construction. */ private Security() { super(); } }