This is the 7th day of my participation in the August Text Challenge.More challenges in August

Data desensitization scheme

1. Desensitization program research

Objective: We will find the data, according to the preset desensitization rules for desensitization, desensitization of the data back to the caller!

After investigation, business party expect little change or not change the code, desensitization and data encryption, at present, need data desensitization and encrypted data base is conducted through JDBC queries, so the amount of code changes on the one hand, consider reducing business, on the one hand, consider the universality of JDBC, so we consider using bytecode pile technology, When the Class is compiled again, JDBC is changed!

The original concrete architecture diagram is as follows:

Our subsequent enhancement business:

Use of raw JDBC

If we want to intercept JDBC, we need to understand JDBC queries. If we have a table like this:

CREATE TABLE `test_sql` (
  `id` int(255) NOT NULL AUTO_INCREMENT,
  `test1` varchar(255) DEFAULT NULL,
  `test2` varchar(255) DEFAULT NULL.PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
Copy the code

JDBC query

/** * Native JDBC tests **@author huangfu
 * @date23 August 2021 09:30:12 */
public class ProtistJdbcTest {
    /** * database url */
    private static final String URL = "JDBC: mysql: / / 10.0.10.118:3306 / huangfu_0601? useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false";
    /** * Database user name */
    private static final String USER_NAME = "root";
    /** * Database password */
    private static final String PASSWORD = "123456";

    public static void main(String[] args) throws Exception {
        // Load the JDBC driver
        Class.forName("com.mysql.cj.jdbc.Driver");
        // Get the connection
        Connection connection = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
        // Verify survival
        boolean valid = connection.isValid(1000);
        System.out.println("Whether the connection is valid :" + valid);
        String sql = "select * from test_sql where id=?";
        // Get the precompiled processor
        PreparedStatement preparedStatement = connection.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
        // Set the condition
        preparedStatement.setInt(1.1);

        // Execute the query
        ResultSet resultSet = preparedStatement.executeQuery();

        // Start querying data
        while (resultSet.next()) {
            System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");
            System.out.println("id:" + resultSet.getInt("id"));
            System.out.println("test1:" + resultSet.getString("test1"));
            System.out.println("test2:" + resultSet.getString("test2"));
            System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); }}}Copy the code

The executeQuery method is executed in a PreparedStatement. The executeQuery method is used to parse the SQL, query the rules for the table fields, and modify the ResultSet based on the rules.

Three, intercept JDBC

1. Architecture legend

2. Code examples

Mysql > alter table test1; alter table test1; alter table test1

We need an interceptor:

/** * listener **@author huangfu
 * @dateAugust 23, 2021 12:04:59 */
public class MysqlJdbcMonitor {
    @RuntimeType
    public static Object intercept(@This Object thisObject, @SuperCallCallable<? > callable) {
        Object call = null;
        try {
            call = callable.call();
            System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the interceptor print start -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -");
            System.out.println("This is what the interceptor printed:" + thisObject);
            System.out.println("Start fetching SQL"); Class<? > aClass = thisObject.getClass(); Method asSql = aClass.getMethod("asSql");
            Object sql = asSql.invoke(thisObject);
            System.out.println(SQL = "******* "; + sql);

            System.out.println("Here is the result set intercepted by the interceptor:" + call);
            if (call instanceof ResultSet) {
                ResultSet resultSet = (ResultSet) call;
                freeFromImprisonment(resultSet);
                System.out.println("Modified type :" + resultSet.getType());


                while (resultSet.next()) {
                    System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- interceptors -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");
                    System.out.println("**********id:" + resultSet.getInt("id"));
                    System.out.println("**********test1:" + resultSet.getString("test1"));
                    System.out.println("**********test2:" + resultSet.getString("test2"));

                    // Get the corresponding field index
                    int waitUpdateColumnIndex = resultSet.findColumn("test1");
                    // Calculate the corresponding data index
                    int waitUpdateColumnIndexUpdate = waitUpdateColumnIndex - 1;
                    //internalRowData
                    ReflectionUtils.doWithFields(resultSet.getClass(), field -> {
                        field.setAccessible(true);
                        Object thisRowDataObject = field.get(resultSet);
                        // Start reading data
                        ReflectionUtils.doWithFields(thisRowDataObject.getClass(), internalRowDataFieldChild -> {
                            internalRowDataFieldChild.setAccessible(true);
                            // Get the value of internalRowData
                            Object internalRowDataByteArray = internalRowDataFieldChild.get(thisRowDataObject);
                            if(internalRowDataByteArray ! =null) {
                                // Get the data of the field
                                byte[][] rowData = (byte[][]) internalRowDataByteArray;
                                // Get the corresponding field data
                                byte[] rowDatum = rowData[waitUpdateColumnIndexUpdate];
                                System.out.println("Initial value:" + new String(rowDatum, StandardCharsets.UTF_8));
                                System.out.println("Change value to: Huangfu Kexing");
                                // Modify the data
                                rowData[waitUpdateColumnIndexUpdate] = "Huang Fu Ke Xing".getBytes(StandardCharsets.UTF_8);
                                internalRowDataFieldChild.set(thisRowDataObject, rowData);
                                System.out.println("------------------ Data modified successfully -------------------");
                            }
                        }, internalRowDataFieldChild -> "internalRowData".equals(internalRowDataFieldChild.getName()));
                    }, field -> "thisRow".equals(field.getName()));

                    System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- interceptors -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");
                }
                // return the corresponding cursor
                resultSet.absolute(0);

            }

            System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the interceptor print end -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -");

        } catch (Exception e) {
            e.printStackTrace();
        }
        return call;
    }

    /** * Resolve ResultSet blocking problem **@paramResultSet resultSet */
    protected static void freeFromImprisonment(ResultSet resultSet) {
        // Change can be rolled
        ReflectionUtils.doWithFields(resultSet.getClass(), field -> {
            field.setAccessible(true);
            Object o = field.get(resultSet);
            System.out.println("The original value was:" + o);
            field.set(resultSet, ResultSet.TYPE_SCROLL_INSENSITIVE);
        }, field -> "resultSetType".equals(field.getName()));

        // Modify read-only
        ReflectionUtils.doWithFields(resultSet.getClass(), field -> {
            field.setAccessible(true);
            Object o = field.get(resultSet);
            System.out.println("The original value was:" + o);
            field.set(resultSet, ResultSet.CONCUR_UPDATABLE);
        }, field -> "resultSetConcurrency".equals(field.getName())); }}Copy the code

We need an interceptor body:

/** * JDBC proxy interceptor **@author huangfu
 * @dateAugust 23, 2021 11:15:10 */
public class PinpointBootStrap {
    public static void premain(String agentArgs, Instrumentation inst) {
        // Build a converter
        AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> builder
                // Intercept static methods prefixed with test
                .method(ElementMatchers.named("executeQuery"))
                / / to entrust
                .intercept(MethodDelegation.to(MysqlJdbcMonitor.class));

        AgentBuilder.Listener listener = new AgentBuilder.Listener() {

            @Override
            public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module.boolean loaded) {}@Override
            public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module.boolean loaded, DynamicType dynamicType) {}@Override
            public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module.boolean loaded) {}@Override
            public void onError(String typeName, ClassLoader classLoader, JavaModule module.boolean loaded, Throwable throwable) {
                throwable.printStackTrace();
            }

            @Override
            public void onComplete(String typeName, ClassLoader classLoader, JavaModule module.boolean loaded) {}};new AgentBuilder
                .Default()
                // Specify the class to intercept
                .type(ElementMatchers.named("com.mysql.cj.jdbc.ClientPreparedStatement"))
                // Add a converter
                .transform(transformer)
                .with(listener)
                // Install the transformation described above into the JavaAgent's intervention.installOn(inst); }}Copy the code

We need to prepare the JavaAgent environment:

   <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>

        <plugin.jar.version>3.2. 0</plugin.jar.version>
        <byte-buddy.version>1.1112.</byte-buddy.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>${byte-buddy.version}</version>
        </dependency>


        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy-agent</artifactId>
            <version>${byte-buddy.version}</version>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.0. 0</version>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <! - specify the packaging position - > < outputDirectory > C: / Users/Administrator/Desktop/asm < / outputDirectory > < transformers > < transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <manifestEntries>
                                        <Premain-Class>com.huangfu.jdbc.PinpointBootStrap</Premain-Class>
                                        <Agent-Class>com.huangfu.jdbc.PinpointBootStrap</Agent-Class>
                                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                                        <Pinpoint-Version>${project.version}</Pinpoint-Version>
                                        <Boot-Class-Path>${project.build.finalName}.jar</Boot-Class-Path>
                                    </manifestEntries>
                                </transformer>
                            </transformers>


                            <artifactSet>
                                <includes>
                                    <include>net.bytebuddy:byte-buddy</include>
                                    <include>net.bytebuddy:byte-buddy-agent</include>
                                </includes>
                            </artifactSet>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF 
      
       META-INF/*.DSA
       
      
       META-INF/*.RSA
               Copy the code

3. Try running it

Run the command:

Java - javaagent:. / bytebuddy - demo - 1.0 - the SNAPSHOT. The jar - cp. / JDBC - demo - 1.0 - the SNAPSHOT. Jar com. Huangfu. JDBC. ProtistJdbcTestCopy the code