`
envoydada
  • 浏览: 65424 次
社区版块
存档分类
最新评论

DES加密

阅读更多
资源读取类
package com.crypto.encrypt;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ResourceBundle;
import java.util.Locale;

  
/**
   * 资源读取类
   
*/
public class Util {

  
private static ResourceBundle resources=null;
  
/**
   * 初始化资源
   
*/
  
static {
        
new Util();
    }


  
public Util() {
    resources 
= ResourceBundle.getBundle(
          
"resource.algorithm",
          Locale.getDefault());
  }
  
/**
   * 读取源文件内容
   * 
@param filename String 文件路径
   * 
@throws IOException
   * 
@return byte[] 文件内容
   
*/
  
public static byte[] readFile(String filename) throws IOException {

    File file 
=new File(filename);
    
if(filename==null || filename.equals(""))
    {
      
throw new NullPointerException("无效的文件路径");
    }
    
long len = file.length();
    
byte[] bytes = new byte[(int)len];

    BufferedInputStream bufferedInputStream
=new BufferedInputStream(new FileInputStream(file));
    
int r = bufferedInputStream.read( bytes );
    
if (r != len)
      
throw new IOException("读取文件不正确");
    bufferedInputStream.close();

    
return bytes;

  }

  
/**
   * 将加密的数据写入文件
   * 
@param data byte[]
   * 
@throws IOException
   
*/
  
public static void writeFile(byte[] data,String filename) throws IOException {
    File file 
=new File(filename);
    file.getParentFile().mkdirs();
    BufferedOutputStream bufferedOutputStream
=new BufferedOutputStream(new FileOutputStream(file));
    bufferedOutputStream.write(data);
    bufferedOutputStream.close();

  }

  
/**
   * 从jar文件里读取class
   * 
@param filename String
   * 
@throws IOException
   * 
@return byte[]
   
*/
  
public byte[] readFileJar(String filename) throws IOException {
    BufferedInputStream bufferedInputStream
=new BufferedInputStream(getClass().getResource(filename).openStream());
    
int len=bufferedInputStream.available();
    
byte[] bytes=new byte[len];
    
int r=bufferedInputStream.read(bytes);
    
if(len!=r)
    {
      bytes
=null;
      
throw new IOException("读取文件不正确");
    }
    bufferedInputStream.close();
    
return bytes;
  }

  
/**
   * 获得密码生成法则
   * 
@return String
   
*/
  
public static String getAlgorithm()
  {
      
return resources.getString("algorithm");
  }
  
/**
   * 获得值
   * 
@param skey String
   * 
@return String
   
*/
  
public static String getValue(String skey)
  {
    
return resources.getString(skey);
  }
}

生成密钥
package com.crypto.encrypt;

import java.security.SecureRandom;
import javax.crypto.KeyGenerator;
import java.security.NoSuchAlgorithmException;
import javax.crypto.SecretKey;
import java.io.*;

public class CreateKey {
  String filename
="";
  
public CreateKey() {

  }

  
/**
   * 获得密匙字节内容
   * 
@throws IOException
   * 
@return byte[]
   
*/
  
public byte[] getKeyByte() throws IOException {
    
byte[] bytes=Util.readFile(filename);
    
return bytes;
  }

  
public void CreateKeyFile(String filename) throws IOException,
      NoSuchAlgorithmException {
    
this.filename=filename;
    
if(filename==null || filename.equals(""))
    {
      
throw new NullPointerException("无效的文件路径");
    }
    createKey();
  }

  
/**
   * 生成密匙
   * 
@throws NoSuchAlgorithmException
   * 
@throws IOException
   
*/
  
private void createKey() throws NoSuchAlgorithmException, IOException {
    SecureRandom secureRandom 
= new SecureRandom();
    
// 为我们选择的DES算法生成一个KeyGenerator对象
    KeyGenerator kg = KeyGenerator.getInstance(Util.getValue("algorithm"));
    kg.init(secureRandom);
    
// 生成密钥
    SecretKey key = kg.generateKey();
    
// 将密钥数据保存为文件供以后使用
    Util.writeFile(key.getEncoded(),filename);
  }

  
/**
   * 获得密匙文件路径
   * 
@return String
   
*/
  
public String getKeyFilePath()
  {
    
return filename;
  }
}

加密
package com.crypto.encrypt;

import java.security.SecureRandom;
import java.io.*;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.Cipher;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import java.lang.reflect.Constructor;
import java.security.spec.KeySpec;
import java.lang.reflect.InvocationTargetException;

public class EncryptData {

  
private String keyfile=null;

  
public EncryptData() {
  }

  
public EncryptData(String keyfile) {
    
this.keyfile=keyfile;
  }

  
/**
   * 加密文件
   * 
@param filename String 源路径
   * 
@param filenamekey String 加密后的路径
   
*/
  
public void createEncryptData(String filename,String filenamekey) throws
      IllegalStateException, IllegalBlockSizeException, BadPaddingException,
      NoSuchPaddingException, InvalidKeySpecException, NoSuchAlgorithmException,
      InvalidKeyException, IOException, InstantiationException,
      IllegalAccessException, IllegalArgumentException,
      InvocationTargetException, NoSuchMethodException, SecurityException,
      ClassNotFoundException, IllegalStateException, IllegalBlockSizeException,
      BadPaddingException, NoSuchPaddingException, InvalidKeySpecException,
      NoSuchAlgorithmException, InvalidKeyException, IOException {
    
//验证keyfile
    if(keyfile==null || keyfile.equals(""))
    {
      
throw new NullPointerException("无效的key文件路径");
    }

    encryptData(filename,filenamekey);
  }

  
/**
   * 加密类文件
   * 
@param filename String 原始的类文件
   * 
@param encryptfile String 加密后的类文件
   
*/
  
private void encryptData(String filename,String encryptfile) throws IOException, InvalidKeyException,
      NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException,
      NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException,
      IllegalStateException, ClassNotFoundException, SecurityException,
      NoSuchMethodException, InvocationTargetException,
      IllegalArgumentException, IllegalAccessException, InstantiationException {

    
//载入待加密的文件
    byte data[]=Util.readFile(filename);
    
// 执行加密操作
    byte encryptedClassData[] = getencryptData(data);
    
// 保存加密后的文件,覆盖原有的类文件。
    Util.writeFile(encryptedClassData,encryptfile);
  }
  
/**
   * 直接获得加密数据
   * 
@param bytes byte[]
   * 
@return byte[]
   
*/
  
public byte[] createEncryptData(byte[] bytes) throws IllegalStateException,
      IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
      NoSuchPaddingException, InvalidKeySpecException, NoSuchAlgorithmException,
      InstantiationException, IllegalAccessException, IllegalArgumentException,
      InvocationTargetException, NoSuchMethodException, SecurityException,
      ClassNotFoundException, IOException {
    bytes
=getencryptData(bytes);
    
return bytes;
  }

          
/**
           * 加密业务方法
           * 
@param bytes byte[] 待加密数据
           * 
@return byte[] 加密后数据
           
*/
      
private byte[] getencryptData(byte[] bytes) throws IOException,
      ClassNotFoundException, SecurityException, NoSuchMethodException,
      InvocationTargetException, IllegalArgumentException,
      IllegalAccessException, InstantiationException, NoSuchAlgorithmException,
      InvalidKeySpecException, NoSuchPaddingException, NoSuchAlgorithmException,
      InvalidKeyException, BadPaddingException, IllegalBlockSizeException,
      IllegalStateException {
    
// 产生一个可信任的随机数源
    SecureRandom sr = new SecureRandom();
    
//从密钥文件key Filename中得到密钥数据
    byte[] rawKeyData = Util.readFile(keyfile);
    
// 从原始密钥数据创建DESKeySpec对象
    Class classkeyspec=Class.forName(Util.getValue("keyspec"));
    Constructor constructor 
= classkeyspec.getConstructor(new Class[]{byte[].class});
    KeySpec dks 
= (KeySpec)constructor.newInstance(new Object[]{rawKeyData});
    
// 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(Util.getAlgorithm());
    SecretKey key 
= keyFactory.generateSecret(dks);
    
// Cipher对象实际完成加密操作
    Cipher cipher = Cipher.getInstance(Util.getAlgorithm());
    
// 用密钥初始化Cipher对象
    cipher.init(Cipher.ENCRYPT_MODE, key, sr);
    
// 执行加密操作
    bytes = cipher.doFinal(bytes);
    
// 返回字节数组
    return bytes;
  }
  
/**
   * 设置key文件路径
   * 
@param keyfile String
   
*/
  
public void setKeyFile(String keyfile)
  {
    
this.keyfile=keyfile;
  }
}

解密
package com.crypto.encrypt;

import java.security.SecureRandom;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.SecretKey;
import javax.crypto.Cipher;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import java.security.spec.KeySpec;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.io.ByteArrayInputStream;

public class UnEncryptData {

  
private String keyfile="";

  
public UnEncryptData() {}

  
public UnEncryptData(String keyfile) {this.keyfile=keyfile;}

  
public void createUnEncryptData(String encryptfile,String filename) throws
      IllegalStateException, IllegalBlockSizeException, BadPaddingException,
      NoSuchPaddingException, InvalidKeySpecException, NoSuchAlgorithmException,
      InvalidKeyException, IOException, NoSuchMethodException,
      SecurityException, InstantiationException, IllegalAccessException,
      IllegalArgumentException, InvocationTargetException,
      ClassNotFoundException, IllegalStateException, IllegalBlockSizeException,
      BadPaddingException, NoSuchPaddingException, InvalidKeySpecException,
      NoSuchAlgorithmException, InvalidKeyException, IOException {
    
//验证keyfile
    if(keyfile==null || keyfile.equals("")){
      
throw new NullPointerException("无效的key文件路径");
    }
    unEncryptData(encryptfile,filename);
  }
  
/**
   * 解密类文件
   * 
@param encryptfile String 经过加密的文件
   * 
@param filename String 解密后的文件
   
*/
  
private void unEncryptData(String encryptfile,String filename) throws
      IOException, IllegalStateException, IllegalBlockSizeException,
      BadPaddingException, InvalidKeyException, NoSuchPaddingException,
      InvalidKeySpecException, NoSuchAlgorithmException, InstantiationException,
      IllegalAccessException, IllegalArgumentException,
      InvocationTargetException, NoSuchMethodException, SecurityException,
      ClassNotFoundException, IOException {
    
// 获得经过加密的数据
    byte[] data = Util.readFile(encryptfile);
    
//执行解密操作
    byte decryptedData[] = getunEncryptData(data);
    
// 然后将解密后的数据转化成原来的类文件。
    Util.writeFile(decryptedData,filename);
  }
  
/**
   * 解密字节数组
   * 
@param bytes byte[]
   * 
@return byte[]
   
*/
  
public byte[] createUnEncryptData(byte[] bytes) throws IllegalStateException,
      IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
      NoSuchPaddingException, InvalidKeySpecException, NoSuchAlgorithmException,
      InstantiationException, IllegalAccessException, IllegalArgumentException,
      InvocationTargetException, NoSuchMethodException, SecurityException,
      ClassNotFoundException, IOException {
    bytes 
= getunEncryptData(bytes);
    
return bytes;
  }
  
/**
   *
   * 
@param bytes byte[]
   * 
@return byte[]
   
*/
  
private byte[] getunEncryptData(byte[] bytes) throws IOException,
      ClassNotFoundException, SecurityException, NoSuchMethodException,
      InvocationTargetException, IllegalArgumentException,
      IllegalAccessException, InstantiationException, NoSuchAlgorithmException,
      InvalidKeySpecException, NoSuchPaddingException, NoSuchAlgorithmException,
      InvalidKeyException, BadPaddingException, IllegalBlockSizeException,
      IllegalStateException {
    
// 生成一个可信任的随机数源
    SecureRandom sr = new SecureRandom();
    
// 从密钥文件中获取原始密钥数据
    byte[] rawKeyData = Util.readFile(keyfile);
    
// 创建一个DESKeySpec对象
    Class classkeyspec=Class.forName(Util.getValue("keyspec"));
    Constructor constructor 
= classkeyspec.getConstructor(new Class[]{byte[].class});
    KeySpec dks 
= (KeySpec) constructor.newInstance(new Object[]{rawKeyData}); //new DESKeySpec(rawKeyData);
    
// 创建一个密钥工厂,然后用它把DESKeySpec对象转换成Secret Key对象
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(Util.getAlgorithm());
    SecretKey key 
= keyFactory.generateSecret(dks);
    
// Cipher对象实际完成解密操作
    Cipher cipher = Cipher.getInstance(Util.getAlgorithm());
    
// 用密钥初始化Cipher对象
    cipher.init(Cipher.DECRYPT_MODE, key, sr);
    
// 获得经过加密的数据
    
//执行解密操作
    bytes = cipher.doFinal(bytes);
    
// 然后将解密后的数据转化成原来的类文件。
    return bytes;
  }
  
public void setKeyFile(String keyfile){this.keyfile=keyfile;}
}

algorithm.properties资源文件
algorithm=DES
keyspec
=javax.crypto.spec.DESKeySpec
keypath
=src/resource/key


测试类

package com.crypto.encrypt;

import java.security.*;

import java.io.*;
import java.lang.reflect.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.*;
import javax.crypto.*;
import sun.security.provider.MD5;

public class TestEncrypt {
    
public TestEncrypt() {
    }

    
/**
     * 创建KEY
     * 
@param keyPath String 生成密钥路径
     
*/
    
public void createKey(String keyPath){
        CreateKey ck 
= new CreateKey();
        
try {
            ck.CreateKeyFile(keyPath);
        } 
catch (NoSuchAlgorithmException ex) {ex.printStackTrace();
        } 
catch (IOException ex) {ex.printStackTrace();
        }
    }

    
/**
     * 加密
     * 
@param sourcePath String 待加密文件路径
     * 
@param distinationPath String 文件加密后保存路径
     * 
@param keyPath String 密匙文件路径
     
*/
    
public void encrypt(String sourcePath,String distinationPath,String keyPath){
        EncryptData enc 
= new EncryptData(keyPath);
        
try {
            enc.createEncryptData(sourcePath, distinationPath);
        } 
catch (ClassNotFoundException ex) {ex.printStackTrace();
        } 
catch (SecurityException ex) {ex.printStackTrace();
        } 
catch (NoSuchMethodException ex) {ex.printStackTrace();
        } 
catch (InvocationTargetException ex) {ex.printStackTrace();
        } 
catch (IllegalArgumentException ex) {ex.printStackTrace();
        } 
catch (IllegalAccessException ex) {ex.printStackTrace();
        } 
catch (InstantiationException ex) {ex.printStackTrace();
        } 
catch (IOException ex) {ex.printStackTrace();
        } 
catch (InvalidKeyException ex) {ex.printStackTrace();
        } 
catch (NoSuchAlgorithmException ex) {ex.printStackTrace();
        } 
catch (InvalidKeySpecException ex) {ex.printStackTrace();
        } 
catch (NoSuchPaddingException ex) {ex.printStackTrace();
        } 
catch (BadPaddingException ex) {ex.printStackTrace();
        } 
catch (IllegalBlockSizeException ex) {ex.printStackTrace();
        } 
catch (IllegalStateException ex) {ex.printStackTrace();
        }
    }

    
/**
     * 解密文件
     * 
@param sourcePath String 待解密文件路径
     * 
@param distinationPath String  解密后文件路径
     * 
@param keyPath String 密钥路径
     
*/
    
public void unEncrypt(String sourcePath,String distinationPath,String keyPath){
       UnEncryptData unEnc 
= new UnEncryptData(keyPath);
    
try {
        unEnc.createUnEncryptData(sourcePath,distinationPath);
    } 
catch (ClassNotFoundException ex) {ex.printStackTrace();
    } 
catch (InvocationTargetException ex) {ex.printStackTrace();
    } 
catch (IllegalArgumentException ex) {ex.printStackTrace();
    } 
catch (IllegalAccessException ex) {ex.printStackTrace();
    } 
catch (InstantiationException ex) {ex.printStackTrace();
    } 
catch (SecurityException ex) {ex.printStackTrace();
    } 
catch (NoSuchMethodException ex) {ex.printStackTrace();
    } 
catch (IOException ex) {ex.printStackTrace();
    } 
catch (InvalidKeyException ex) {ex.printStackTrace();
    } 
catch (NoSuchAlgorithmException ex) {ex.printStackTrace();
    } 
catch (InvalidKeySpecException ex) {ex.printStackTrace();
    } 
catch (NoSuchPaddingException ex) {ex.printStackTrace();
    } 
catch (BadPaddingException ex) {ex.printStackTrace();
    } 
catch (IllegalBlockSizeException ex) {ex.printStackTrace();
    } 
catch (IllegalStateException ex) {ex.printStackTrace();
    }
    }

    
public static void main(String[] args) {
        TestEncrypt e 
= new TestEncrypt();
        e.createKey(
"classes\\resource\\key1");
        e.encrypt(
"classes\\resource\\a.txt","classes\\resource\\ena.txt","classes\\resource\\key1");
        e.unEncrypt(
"classes\\resource\\ena.txt","classes\\resource\\una.txt","classes\\resource\\key1");
    }
}
分享到:
评论

相关推荐

    des加密解密_Des加密解密_DES加密_

    在给定的“des加密例程”中,可能包含了一个调用动态链接库(DLL)实现DES加密解密的示例代码。DLL是Windows操作系统中的一种共享库,可以被多个程序同时调用,节省内存资源并便于代码复用。这个示例可能涉及以下...

    3DES加密解密工具

    标题中的“3DES加密解密工具”指的是一个用于执行三重数据加密标准(3DES,Triple DES)的软件工具,这种工具通常用于保护敏感数据的安全,确保信息在传输过程中的机密性。3DES是DES(Data Encryption Standard,...

    C语言实现DES加密解密算法

    DES加密解密算法的C语言实现 DES(Data Encryption Standard)是一种对称密钥 BLOCK 加密算法,使用 56 位密钥对 64 位数据块进行加密。下面是 DES 加密解密算法的 C 语言实现的知识点总结。 字节与二进制转换 在...

    VB实现DES加密解密算法,vb加密和解密,VBA

    本篇文章将详细探讨如何在VB环境中实现DES加密和解密,以及相关的VBA(Visual Basic for Applications)应用。 首先,DES是一种块加密算法,它的基本工作原理是将明文数据分为64位的块,然后通过一系列复杂的数学...

    STM32上实现D3DES加密

    6. **执行D3DES**:首先用第一个密钥执行DES加密,然后用第二个密钥执行DES解密,最后再用第一个密钥进行一次DES加密。这个过程就是D3DES的核心。 7. **密文后处理**:加密完成后,对密文进行逆初始置换,得到最终...

    DES加密_delphi_加解密_des_

    在Delphi编程环境中,实现DES加密和解密是一项常见的任务,特别是在处理敏感信息时。 标题"DES加密_delphi_加解密_des_"所涉及的核心知识点是: 1. **DES算法**:DES是一种64位块密码,使用56位的密钥进行加密。它...

    DES加密算法(c语言实现)

    DES加密算法的工作原理主要包括以下几个步骤: 1. **初始置换**:原始64位的数据块首先经过一个固定的初始置换,使得数据分布更均匀,增强安全性。 2. **16轮迭代**:每轮迭代都包括四个子步骤: - **子密钥产生*...

    sql server 中进行des加密

    在SQL Server中进行DES加密是保护敏感数据的一种常见方法,特别是在C#应用程序中与数据库交互时。DES(Data Encryption Standard)是一种对称加密算法,它使用相同的密钥进行加密和解密,提供了一种相对快速的数据...

    3DES加密js前端示例

    3DES(Triple Data Encryption Algorithm,三重数据加密算法)是一种强化版的DES(Data Encryption Standard)加密算法,它通过三次应用DES加密过程来提高安全性。在JavaScript中实现3DES加密通常是为了在网络通信中...

    实验一 基于DES加密的TCP聊天程序1

    在本文中,我们将深入探讨基于DES加密的TCP聊天程序的实现,以及DES算法和TCP协议的相关知识。DES(Data Encryption Standard)是一种广泛应用的对称加密算法,它的历史可以追溯到20世纪70年代,由IBM的Lucifer算法...

    DES加密DEMO(C#与JAVA)

    本DEMO提供了C#和JAVA两种编程语言下的DES加密和解密实现,这对于理解DES算法及其在实际开发中的应用非常有帮助。 1. **DES算法原理** - **结构**:DES算法基于Feistel网络,它将明文分为左右两半,通过一系列的...

    Qt写的DES加密算法展示程序

    **Qt DES加密算法展示程序详解** Qt是一个跨平台的C++图形用户界面应用程序开发框架,它为开发者提供了丰富的工具和库,使得构建GUI应用变得容易。在这个“Qt写的DES加密算法展示程序”中,我们将深入探讨DES(Data...

    DES加密算法—实现(C语言)

    在C语言中实现DES加密算法,你需要理解以下几个关键知识点: 1. **DES结构**:DES算法分为两个主要部分——初始置换(IP)和逆初始置换(IP^-1),以及16轮的Feistel网络。每轮包含子步骤如扩展置换(E-Box)、选择...

    nodejs实现3des(2倍长)加密方式,与DES加密工具一致

    使用`crypto.createCipheriv`方法创建3DES加密器,需要指定加密算法('des-ede3'代表3DES),初始化向量(IV,通常为随机生成的8字节序列),以及密钥。例如: ```javascript const iv = Buffer.alloc(8, 0); // ...

    C++ 代码实现DES加密解密源代码类

    在C++中实现DES加密解密,可以创建一个类来封装相关的操作,这样有利于代码的复用和维护。 描述中提到的"单倍双倍加密算法的实现",可能是指使用DES算法进行一次或两次加密的过程。单次DES加密使用同一个密钥对数据...

    Java实现文件的RSA和DES加密

    Java 实现文件的 RSA 和 DES 加密 在现代密码技术中,根据密钥类型的不同,可以将其分为两类:对称加密算法(秘密钥匙加密)和非对称加密算法(公开密钥加密)。对称加密算法用来对敏感数据等信息进行加密,常用的...

    基于des加密的tcp聊天1

    实验报告——基于DES加密的TCP聊天程序 一、实验目的 本次实验的主要目的是让学生深入理解DES(Data Encryption Standard)加密算法的原理,并将其应用于TCP(Transmission Control Protocol)通信中,以增强网络...

    DES加密算法C++实现

    在C++中实现DES加密算法,通常涉及到以下几个关键知识点: 1. **DES算法原理**:DES算法基于Feistel结构,通过16轮迭代变换将64位明文数据转化为64位密文。每一轮迭代包含一个子密钥生成过程和一个替换-置换过程。...

    java和javascript之间的DES加密解密

    Java和JavaScript之间的DES加密解密是信息安全领域中的一个重要话题,主要涉及到数据的保护和通信的安全。DES(Data Encryption Standard)是一种古老的对称加密算法,尽管它在安全性上已不被视为最佳选择,但在某些...

    字符串DES加密解密,可自定义KEY和向量IV

    标题中的“字符串DES加密解密,可自定义KEY和向量IV”指的是使用DES(Data Encryption Standard)算法对字符串进行加密和解密的过程,其中用户可以自由设定密钥(Key)和初始向量(Initialization Vector,简称IV)...

Global site tag (gtag.js) - Google Analytics